共享引用——共享引用和相等
为了充分披露信息,应该指出:对某些类型,本章之前描述的垃圾回收行为可能是概念化的而非很精确的。考虑下面这些语句:
>>> 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为执行速度而优化其模型的多种方式之一。