第八章 对象引用、可变性和垃圾回收

  在python中,可以把变量比喻为一个标签,而真正的对象是一个盒子,这个盒子上可以贴上多个不同的标签,如果这个盒子上没有标签,那么代表着这个盒子是个已经废弃的无用盒子。

在==和is之间选择

  == 运算符比较两个对象的值(对象中保存的数据),而 is 比较对象的标识。
  通常,我们关注的是值,而不是标识,因此 Python 代码中 == 出现的频率比 is 高。
  然而,在变量和单例值之间比较时,应该使用 is。目前,最常使用 is 检查变量绑定的值
是不是 None。下面是推荐的写法:

x is None
或者
x is not None

  is 运算符比 == 速度快,因为它不能重载,所以 Python 不用寻找并调用特殊方法,而是直接比较两个整数 ID。而 a == b 是语法糖,等同于 a.eq(b)。继承自 object 的__eq__ 方法比较两个对象的 ID,结果与 is 一样。但是多数内置类型使用更有意义的方式覆盖了 eq 方法,会考虑对象属性的值。

元组的相对不可变性

  元组与多数 Python 集合(列表、字典、集,等等)一样,保存的是对象的引用。 如果引用的元素是可变的,即便元组本身不可变,元素依然可变。也就是说,元组的不可变性其实是指 tuple 数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。

默认做浅复制

>>> l1 = [3, [55, 44], (7, 8, 9)]
>>> l2 = list(l1) 
>>> l2
[3, [55, 44], (7, 8, 9)]
>>> l2 == l1 
True
>>> l2 is l1 
False

为任意对象做深复制和浅复制

  浅复制没什么问题,但有时我们需要的是深复制(即副本不共享内部对象的引用)。copy 模块提供deepcopy 和 copy 函数能为任意对象做深复制和浅复制。

函数的参数作为引用时

  Python 唯一支持的参数传递模式是共享传参(call by sharing)。多数面向对象语言都采用这一模式,包括 Ruby、Smalltalk 和 Java(Java 的引用类型是这样,基本类型按值传参)。
  共享传参指函数的各个形式参数获得实参中各个引用的副本。也就是说,函数内部的形参是实参的别名
  这种方案的结果是,函数可能会修改作为参数传入的可变对象,但是无法修改那些对象的标识(即不能把一个对象替换成另一个对象)。
  可选参数可以有默认值,这是 Python 函数定义的一个很棒的特性,这样我们的 API 在进化的同时能保证向后兼容。然而,我们应该避免使用可变的对象作为参数的默认值。

def func(a, b c=[]):
	"""这么做会改变外部传入的可变参数,导致数据混乱"""
	print(a,b,c)
	
# 正确做法(防御可变参数)
def func(a,b,c=None):
	if c is None:
		c = []

del和垃圾回收

  del 语句删除名称,而不是对象。del 命令可能会导致对象被当作垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用,或者无法得到对象时。 重新绑定也可能会导致对象的引用数量归零,导致对象被销毁。

有个 __del__特殊方法,但是它不会销毁实例,不应该在代码中调用。即将 销毁实例时,Python 解释器会调用 __del__方法,给实例最后的机会,释放外部资源。

  在 CPython 中,垃圾回收使用的主要算法是引用计数。实际上,每个对象都会统计有多少引用指向自己。当引用计数归零时,对象立即就被销毁:CPython 会在对象上调用__del__ 方法(如果定义了),然后释放分配给对象的内存。CPython 2.0 增加了分代垃圾回收算法,用于检测引用循环中涉及的对象组——如果一组对象之间全是相互引用,即使再出色的引用方式也会导致组中的对象不可获取。Python 的其他实现有更复杂的垃圾回收程序,而且不依赖引用计数,这意味着,对象的引用数量为零时可能不会立即调用__del__ 方法

弱引用

  正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。但是,有时需要引用对象,而不让对象存在的时间超过所需时间。这经常用在缓存中。
  弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。
  弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。

Python对不可变类型施加的把戏

  对元组 t 来说,t[:] 不创建副本,而是返回同一个对象的引用。此外,tuple(t) 获得的也是同一个元组的引用。

>>> t1 = (1, 2, 3)
>>> t2 = tuple(t1)
>>> t2 is t1 
True
>>> t3 = t1[:]
>>> t3 is t1 
True

  str、bytes 和 frozenset 实例也有这种行为。注意,frozenset 实例不是序列,因此不能使用 fs[:](fs 是一个 frozenset 实例)。但是,fs.copy() 具有相同的效果:它会欺骗你,返回同一个对象的引用,而不是创建一个副本,

字符串字面量可能会创建共享的对象


>>> t1 = (1, 2, 3)
>>> t3 = (1, 2, 3) 
>>> t3 is t1 
False
>>> s1 = 'ABC'
>>> s2 = 'ABC' 
>>> s2 is s1 
True

  共享字符串字面量是一种优化措施,称为驻留(interning)。CPython 还会在小的整数上使用这个优化措施,防止重复创建“热门”数字,如 0、-1 和 42。
注意:CPython 不会驻留所有字符串和整数,驻留的条件是实现细节,而且没有文档说明。

不要依赖字符串或整数的驻留!比较字符串或整数是否相等时,应该使用 ==,而不是 is。驻留是 Python 解释器内部使用的一个特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值