变量视作便利贴
赋值:把变量分配给对象。先有对象
Python 变量类似 Java 中的引用式变量
标识
对象一旦创建,它的标识绝不会更改
可以把标识理解为内存地址,is 运算符比较两对象的标识;id() 返回对象的标识的整数表示
is 与 ==
- == 比较的是值,is 比较的是两个整数 id
- is 比 == 快,因为 is 不能重载,而 == 背后是 a._eq_(b),object 的 _eq_ 比较的是两对象的 id,与 is 一致。
但多数内置类型覆盖了此方法,考虑对象的属性的值,为此做相等性测试可能涉及大量处理工作。 - 变量与单例值比较应使用 is。 x is None;x is not None
tuple 的相对不可变性
- 容器序列保存的是对象的引用
单一数据类型的扁平序列在连续内存中保存的是数据本身 -
tuple 不可变指 tuple 的物理内容(保存的引用)不可变。与引用的对象无关!
默认做浅复制
-
浅复制:只复制最外层容器,副本中的元素是源容器元素的引用
使用构造方法或 [ : ] 实现
- 对于可变对象元素,由于存放的是引用,复制的也是引用。一个列表改变,另外一个列表也改变。
- 对于不可变对象元素,虽然存放的是引用,复制的也是引用。
但只要发生改变,实质是创建了新对象,原来的对象还是不会变。自然不会影响到另外一个列表
深复制
深复制:副本【与源本不共享内部对象】copy.deepcopy(object)
浅复制:copy.copy(object),背后分别是 _copy_()、_deepcopy_()
参数传递
Python 使用的是共享传参(call by sharing),即形参是实参的别名
- x 是不可变对象,当 x += y 时,实质创建了新对象,且新对象的作用域只在函数内,原来的 x 没变。
- p 是可变对象,当 p += q 时,p 已经变了。
不要使用可变对象作为函数默认值
-
默认值在定义函数时计算(通常在加载模块时,也就是导入模块时),即 passen=[] 只在加载模块时执行一次,
因此默认值变成了函数这个对象的属性。
如果默认值是可变对象,而且修改了它的值,后续函数调用都会受到影响。
- 所以通常使用 None 作为接受可变值参数的默认值
防御可变参数
def __init__(self, passengers=None):
if passengers is None:
passen = []
else:
self.passengers = list(passengers)
- 用 None 作为接受可变值参数的默认值
- 如果直接 self.passengers = passengers 还是不够妥当,因为这样形参和实参共享同一个对象,形参变了,实参也会发生改变。list(passengers) 相当浅复制了 passengers。反正一句话,不要直接对形参进行操作,否则会影响实参。应该生成形参副本再操作。
del 和垃圾回收
引用计数:
CPython 垃圾回收算法。当对象的引用归零时,CPython 会在对象上调用_del_ 方法(前提是定义了),然后释放分配给对象的内存。
分代垃圾回收
CPython 2.0 增加的算法。
如果一组对象全是相互引用,如: a = [2, 3], b= [a, 5], a.append(b)
即使再出色的引用方式也会导致组中对象不可获取。
- weakref.finalize(s1, bye):在 s1 引用的对象上注册 bye 回调函数
弱引用
- 上例中,finalize 持有 {1, 2, 3} 的弱引用
- 在「缓存」中,经常要引用对象,却不让对象存在时间超过所需时间
-
弱引用不会增加对象引用数量,弱引用引用的对象称为「所指对象 referent」。弱引用不会影响 referent 被当作垃圾回收
- wref() 会返回被引用对象,因为这是控制台会话,返回的对象会绑定到 _ 变量
- 即使删除了引用 a,仍有引用 _ 绑定对象 {0, 1}
-
当使用 wref() is None 时,_ 会绑定返回值 False,这时对象{0, 1} 就没有引用了
WeakValueDictionary
- weakref.ref 类是底层接口(少用),较常用的是 finalize 和 weakref 集合(WeakKeyDictionary、WeakValueDictionary、WeakSet)
- WeakValueDictionary 类实现的是可变映射,
里面的值是对象的弱引用,被引用的对象被回收后,
对应的键自动从 WeakValueDictionary 中删除。
常用作「缓存」
- stock 是WeakValueDictionary 的一个实例,值是对象的弱引用。
- del catalog 意味着被引用的对象被回收,按道理来说,stock 的键也会自动删除,但是最后一个键被保留了。
- 这时因为 for 循化中的 cheese 是全局变量,循环结束后绑定着对象 Cheese('ccc'),所以del catalog 后,对象 Cheese('ccc') 仍有引用 cheese,所以不被当作垃圾回收,直至 del cheese 后才被当作垃圾回收,对应的键也就自动删除。
weakSet
保存元素弱引用的集合类,当元素没有强引用时,自动删除该元素。
对不可变对象的优化
- 使用一个元组创建另外一个元组(tuple()、[:]、copy、deepcopy),
得到的是同一个对象。 - 共享字符串字面量是一种优化措施,称为「驻留 interning」
- 类似的还有 bytes、frozenset 实例、较小的整数,这样能节省内存,提高解释器速度。其实不了解也无伤大雅。
杂谈
- 从 object 继承的_eq_ 方法(即 == 运算符)比较的是对象 id
- 用户创建的类,其实例默认可变
- 可变对象是导致多线程编程难以处理的主要原因。某个线程改动对象后,不正确同步则损坏数据,过度同步又导致死锁。
- Python 没有手动销毁对象的机制。这是个好特性:如果能手动销毁对象,那么指向对象的强引用就不知怎么处理了。
- CPython 中,这样写是安全的:
open('test.txt', 'wt', encoding='utf-8').write('1, 2, 3')
因为文件对象的引用数量在 write 方法返回后归零,销毁内存中文件对象之前,会立即关闭文件。而在 Jpython 或 IronPython 中,open().write() 却是不安全的,因为它们不依靠引用计数。