《Python高级编程》学习心得——第七章 对象引用、可变性与垃圾回收
本章的内容与我之前写过的一篇博文:python对象赋值、浅复制、深复制的区别十分相关,可以对照着看。
对象可变性
Python中的对象分为可变对象和不可变对象,常见类型中int, str, tuple是不可变对象,list, deque, set是可变对象。可变对象和不可变对象在实现机制上有本质差别。
Python“一切皆对象”,因而Python中的变量是对象的一个引用。这一点与Java不同,Java中仍保留了基本类型如int, float等,Java的int,float变量是直接存储在变量所占的栈空间的。而Python中所有对象都在堆区,栈区的变量是堆区对象的一个引用。
对于不可变对象,我们不可以通过栈区的变量(也就是堆区对象的引用)来直接改变堆区对象的内容,如果我们想要让一个变量指向的堆区对象的内容发生改变,唯一能做的就是在堆区重新开辟一个空间,构造一个新的对象,再将这个对象的赋值给该变量。
a = 1
aid_0 = id(a)
a = 3
aid_1 = id(a)
print(aid_0 == aid_1) # False
对于可变对象,我们可以通过栈区的变量来改变堆区对象的内容。
a = [1, 2]
aid_0 = id(a)
a.append(3)
aid_1 = id(a)
print(aid_0 == aid_1) # True
函数调用时会发生参数传递,但传递的参数仅仅是栈区的引用实参的值拷贝给了栈区的引用形参,因此不可变对象作形参,函数内部对形参改变不会使得函数体外实参的值发生变化,不可变对象的引用无法改变堆区对象的内容;而可变对象作形参,函数内部对形参的改变会改变堆区对象的内容,从而改变实参的值。
x def add(a, b): a += b a = 1 # immutable objectb = 2add(a, b)print(a) # 1, unchangeda = [1] # mutable objectb = [2]add(a, b)print(a) # [1, 2], changedpython
== 与 is 的区别
一言以蔽之,**==调用的是对象的__eq__**方法,而"a is b"等价于"id(a) == id(b)". 笼统地说,前者是判断两个对象的值是否相同,后者是判断两个对象的内存地址是否相同。
a = [1, 2]
b = a
id(a) == id(b) # True
a is b # True
a == b # True
a = [1, 2]
b = [1, 2]
id(a) == id(b) # False
a is b # False
a == b # True
比较特殊的有两类对象,一类是小整数(不包括大整数)和短字符串,Python内存管理中为小整数和短字符串开辟了一个专门的列表(类似于Java中的字符串常量池),其中每一个地址就对应一个唯一的小整数或短字符串对象,当变量指向每一个小整数或短字符串对象时,只要将该对象在常量池中的地址赋给这个变量即可。因此,多个被赋值为相同的小整数或短字符串的变量id是相等的。
a = 1
b = 1
id(a) == id(b) # True
a is b # True
a = 'HIT'
b = 'HIT'
id(a) == id(b) # True
a is b # True
a = 300
b = 300
id(a) == id(b) # False
a is b # False
另一类是类对象。每个类只有一个全局唯一的类对象。
a = [1, 2]
b = [3, 4]
ac = type(a)
bc = type(b)
id(ac) == id(bc) # True
ac is bc # True
del 与 垃圾回收
与C++中的delete关键字会直接回收对象不同,Python的del关键字只会删除对象的引用,Python对象的回收仍由Python的GC(垃圾回收)机制负责。关于Python的垃圾回收机制,又可以引出长篇大论,在此就不节外生枝了。
a = 1
b = a
del a
print(b) # 1