变量是标注,而不是盒子。

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() 返回被引用的对象,在被回收后返回 Noneweakref.ref 类是底层接口,一般不手动创建,可以使用 weakref 集合和 finalize 函数
  • WeakValueDictionary 实现可变映射,值为弱引用,使用时类似字典。当对象被回收时,自动删除对应的键值对;
  • WeakKeyDictionary键为弱引用,可以为其他部分的拥有对象附加数据,而不添加属性;
  • WeakSet 是保存元素弱引用的集合类,通常用来保存所有实例的引用,判断是否被回收;
  • 不是所有的 Python 对象都能作为弱引用的目标(所指对象),如 listdict(它们的子类可以)、inttuple 不行,set 可以。

8.7 Python 与不可变类型

  • 对不可变的元组 tuplestrbytesfrozenset 实例使用 [: ].copy() 方法或构造函数进行复制操作时,返回的是同一个对象的引用
  • 字符串字面量可能被共享,这称为驻留:使用同一字符串字面量赋值给两个变量,第二次赋值没有重新创建字符串对象,而是直接将第一次的对象赋值给变量,两个变量共享同一字符串对象。这与同为不可变的元组等类型不同:后者需要先创建对象,再进行赋值,因此变量不同;
  • 整数字面量也存在驻留现象,较小的整数对象也会被赋值给多个变量;
  • 两个不可变对象是同一对象还是副本,这对实际使用没有区别,因此 CPython 可进行优化。