Chapter 8 Object References, Mutability, and Recycling
变量是标注,而不是盒子。
8.1 变量不是盒子
- 对象在赋值之前就创建了。赋值语句的右边先执行,计算完后再将其赋值给左侧变量。
8.2 标识、相等性和别名
- 两个不同的对象可能相等,这取决于
__eq__方法(比较的是值)。但它们永远不是同一对象。 - 在变量和单例值之间比较时,应该使用
is,如x is None。这是因为该操作不能重载,直接比较 ID 即可,而等号则是__eq__特殊方法的调用,可能很复杂。
8.3 浅复制与深复制
- 对于元组使用
+=运算符,需要创建一个新元组,并重新绑定变量。而列表的操作是原地的; - 如果对象有循环引用,为了不进入无限循环,
deepcopy会记住已复制的对象(循环引用在复制之后指向新的对象,仍然保持循环); - 可以实现
__copy__()和__deepcopy__()特殊方法来控制复制的行为。
8.4 函数的参数作为引用
- 共享传参:各个形式参数获得实参中各个引用的副本,因此是实参的别名(同一对象);
- 不要使用可变的对象作为默认参数值:如果在函数中对这一参数进行操作,它改变的是默认参数值。默认值是函数对象的一个属性(存在
__defaults__字典中),因此后续调用函数时的默认参数值发生了变化。通常使用None作为接受可变值的参数; - 在类中直接把参数赋值给实例变量前需要三思:这样会为参数对象创建别名,而非复制。
8.5 del 和垃圾回收
del语句删除名称,而不是对象。仅当删除的变量保存的是对象的最后一个引用,或无法得到的对象(如两个对象互相引用且无其他引用)时,对象会被回收。重新绑定引用的效果类似;__del__特殊方法一般很少需要实现,用来在销毁实例前自动调用;- 可以用
weakref.finalize()函数注册回调函数,在销毁对象时调用该函数。虽然在注册时传入了对象的引用,增加了其引用的数目,但finalize中包含的是弱引用,见下一节。
8.6 弱引用
- 注意:Python 控制台会自动把
_变量绑定到上一结果不为None的表达式结果上,增加引用; - 注意:
for迭代过程中创建的变量的作用域是整个上下文,而非循环内的局部变量; weakref.ref()构造函数可以创建弱引用对象,它不会增加对象的引用数量,不会影响垃圾回收。wref()返回被引用的对象,在被回收后返回None。weakref.ref类是底层接口,一般不手动创建,可以使用weakref集合和finalize函数;WeakValueDictionary实现可变映射,值为弱引用,使用时类似字典。当对象被回收时,自动删除对应的键值对;WeakKeyDictionary的键为弱引用,可以为其他部分的拥有对象附加数据,而不添加属性;WeakSet是保存元素弱引用的集合类,通常用来保存所有实例的引用,判断是否被回收;- 不是所有的 Python 对象都能作为弱引用的目标(所指对象),如
list和dict(它们的子类可以)、int和tuple不行,set可以。
8.7 Python 与不可变类型
- 对不可变的元组
tuple、str、bytes、frozenset实例使用[: ]、.copy()方法或构造函数进行复制操作时,返回的是同一个对象的引用; - 字符串字面量可能被共享,这称为驻留:使用同一字符串字面量赋值给两个变量,第二次赋值没有重新创建字符串对象,而是直接将第一次的对象赋值给变量,两个变量共享同一字符串对象。这与同为不可变的元组等类型不同:后者需要先创建对象,再进行赋值,因此变量不同;
- 整数字面量也存在驻留现象,较小的整数对象也会被赋值给多个变量;
- 两个不可变对象是同一对象还是副本,这对实际使用没有区别,因此 CPython 可进行优化。