对象引用、可变性、垃圾回收

变量视作便利贴

变量 a,b 引用同一个列表

赋值:把变量分配给对象。先有对象
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 的物理内容(保存的引用)不可变。与引用的对象无关!


    区分赋值与 append

默认做浅复制

  • 浅复制:只复制最外层容器,副本中的元素是源容器元素的引用
    使用构造方法或 [ : ] 实现


    浅复制
  • 对于可变对象元素,由于存放的是引用,复制的也是引用。一个列表改变,另外一个列表也改变。
  • 对于不可变对象元素,虽然存放的是引用,复制的也是引用。
    但只要发生改变,实质是创建了新对象,原来的对象还是不会变。自然不会影响到另外一个列表

深复制

深复制:副本【与源本不共享内部对象】copy.deepcopy(object)
浅复制:copy.copy(object),背后分别是 _copy_()、_deepcopy_()

参数传递

Python 使用的是共享传参(call by sharing),即形参是实参的别名


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)
即使再出色的引用方式也会导致组中对象不可获取。

del 不会删除对象,但 del 导致对象不可获取从而被删除

  • weakref.finalize(s1, bye):在 s1 引用的对象上注册 bye 回调函数

弱引用

  • 上例中,finalize 持有 {1, 2, 3} 的弱引用
  • 在「缓存」中,经常要引用对象,却不让对象存在时间超过所需时间
  • 弱引用不会增加对象引用数量,弱引用引用的对象称为「所指对象 referent」。弱引用不会影响 referent 被当作垃圾回收


    弱引用
  • wref() 会返回被引用对象,因为这是控制台会话,返回的对象会绑定到 _ 变量
  • 即使删除了引用 a,仍有引用 _ 绑定对象 {0, 1}
  • 当使用 wref() is None 时,_ 会绑定返回值 False,这时对象{0, 1} 就没有引用了


    _ 自动绑定 wref() 返回对象

WeakValueDictionary

  • weakref.ref 类是底层接口(少用),较常用的是 finalize 和 weakref 集合(WeakKeyDictionary、WeakValueDictionary、WeakSet)
  • WeakValueDictionary 类实现的是可变映射,
    里面的值是对象的弱引用,被引用的对象被回收后,
    对应的键自动从 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() 却是不安全的,因为它们不依靠引用计数。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容