共享引用——共享引用和相等
为了充分披露信息,应该指出:对某些类型,本章之前描述的垃圾回收行为可能是概念化的而非很精确的。考虑下面这些语句:
>>> x = 42
>>> x = 'shrubbery' # 现在回收42了?
前面提到:因为 Python 缓存和重用了小整数和小字符串,这里的对象 42 很可能不被真的回收;相反,它将很可能保留在一个系统表中,在代码下次产生 42 时被重用。然而,大多数对象,当它们不被引用时就被立即回收;对于那些不被立即回收的,缓存机制也和代码无关。
比如,因为 Python 的引用模型,在 Python 程序中有两种不同方法来检查相等。创建一个共享引用来展示:
>>> L = [1, 2, 3]
>>> M = L # M 和 L 引用同一对象
>>> L == M # 值相同
True
>>> L is M # 对象相同
True
这里第一个技术,==
操作符,测试两个被引用对象是否有相同值;这是 Python 中几乎总是用来进行相等检查的方法。第二个方法,is
操作符,测试对象相等 —— 如果两个名称指向相同的对象,它才返回 true
,所以它是一个强很多的相等测试形式,在大多数程序中很少使用。
真的,is
简单地比较实现引用的指针,而且如果需要,它作为代码中探测共享引用的一种方式。如果名称指向等价但不同的对象,它返回 False,和运行两个不同字面量表达式时的情况一样:
>>> L = [1, 2, 3]
>>> M = [1, 2, 3] # M 和 L 引用不同对象
>>> L == M # 值相同
True
>>> L is M # 对象不同
False
现在,观察当对小数字执行同样操作时会发生什么:
>>> X = 42
>>> Y = 42 # 应该是两个不同对象
>>> X == Y
True
>>> X is Y # 不管怎样是同样的对象: 缓存起作用了!
True
在这个交互中,X 和 Y 应该 == (值相等),但不是 is (同样的对象)因为运行了两个字面量表达式(42)。然而, 因为小数字和字符串会被缓存和重用,is
告诉我们它们引用了同样的单个对象。
事实上,如果真的想了解原理,可以总是问 Python 一个对象有多少个引用:在标准 sys
模块中的 getrefcount
函数返回了对象的引用数。比如,当在 IDLE GUI 中询问整数对象 1 时,它报告了这同一个对象的 647 个重用(大多数是在 IDLE 的系统代码中,而不是在我的代码中,然而在 IDLE 之外这个操作也返回了 173,所以 Python 一定也在囤积 1):
>>> import sys
>>> sys.getrefcount(1) # 这个共享内存片段的647个指针
647
这种对象缓存和重用和代码无关(除非运行 is
检查!)。因为不能就地修改不可变的数字或字符串,对同一个对象有多少引用是没有关系的 —— 每个引用将总是看到同样的,不变的值。然而,这个行为反应了 Python 为执行速度而优化其模型的多种方式之一。
推荐文章: