Python学习之共享引用
Python 学习之共享引用
什么是共享引用
假设我们在Python交互模式下输入以下语句:
>>> a = 3
>>> b = a
实际的效果就是变量a和b都引用了相同的对象(指向了相同的内存空间)。这在Python中叫做共享引用——多个变量名引用了同一个对象。
如果再来一条语句
>>> a = 'spam'
会怎么样?
变量a引用了由常量表达式‘spam’所创建的新对象,但是变量b仍然引用原始的对象3,因为这个赋值运算改变的不是对象3,它仅仅改变了变量a的指向,变量b并没有发生改变。
在Python中,变量总是一个指向对象的指针,而不是可改变的内存区域的标签(比如C语言中的变量)。给一个变量赋一个新值,并不是修改了原始的对象,而是让这个变量去引用完全不同的一个对象。
注意:当可变的对象以及原处的改变进入这个场景,上述情形会有某种改变。
共享引用和在原处修改(Shared References and In-Place Changes)
有一些对象和操作确实会在原处改变对象。例如,在一个列表中通过偏移进行赋值,这确实会改变这个列表对象,而不是生成一个新的列表对象。对于支持这种在原处修改的对象,共享引用的时候一定要小心,因为对一个变量的修改会影响其他变量。
请看下面的语句:
>>> L1 = [2, 3, 4]
>>> L2 = L1
L1 是一个包含了对象2、3、4的列表。列表中的元素是通过他们的位置进行读取的,所以L1[0]
引用对象2. 当然,列表自身也是对象,就像整数和字符串一样。在运行了上面两行语句后,L1和L2引用了相同的对象。
现在加上第3行:
>>> L1 = [2, 3, 4]
>>> L2 = L1
>>> L1[0] = 24
我们看一下L1和L2的值:
>>> L1
[24, 3 ,4]
>>> L2
[24, 3 ,4]
在这里,没有改变L1,改变了L1所引用的对象的一个元素,这类修改会覆盖列表对象中的某部分。因为这个列表对象同时被L1和L2引用,所以在原处修改不仅仅会影响L1,也会影响L2。虽然我们没有改变L2,但是它的值将发生变化。
如果你不想要这样的结果,那么需要拷贝对象,而不是创建引用。有很多拷贝列表的办法,最简单的办法是从头到尾的切片。
>>> L1 = [2, 3, 4]
>>> L2 = L1[:]
>>> L1[0] = 24
>>> L1
[24, 3, 4]
>>> L2
[2, 3, 4]
这里,对L1的修改不会影响L2,因为L2引用的是L1所引用对象的一个拷贝。也就是说,L1和L2指向了不同的内存区域。
共享引用和相等
>>> x = 42
>>> x = 'shrubbery'
因为Python缓存并复用了小的整数和小的字符串,执行完这两行代码后,对象42也许不会被回收;相反地,它可能仍被保存在一个系统表中,等待下一次你的代码生成另一个42来重复利用。尽管这样,大多数种类的对象都会在不被引用的时候马上回收。
在Python中有2种不同的方法去检查两个变量是否相等。
>>> L = [1, 2, 3]
>>> M = L # M and L reference the same object
>>> L == M # Same values
True
>>> L is M # Same objects
True
==
操作符测试两个被引用的对象是否有相同的值,这种方法往往在Python中用作相等的检查。
is
操作符用来检查对象的同一性。如果两个变量名都指向同一个对象,则会返回 True,所以这是一种更严格的相等测试。
实际上,is
只是比较实现引用的指针,所以这是一种检测共享引用的方法。如果变量名指向不同的对象,就算这两个对象的值相等,也会返回 False.
例如:
>>> L = [1, 2, 3]
>>> M = [1, 2, 3] # M and L reference different objects
>>> L == M # Same values
True
>>> L is M # Different objects
False
如果对小的数字进行类似测试:
>>> X = 42
>>> Y = 42 # Should be two different objects
>>> X == Y
True
>>> X is Y # Same object anyhow: caching at work!
True
按理来说,第3~4行是可以理解的,因为两个对象的值一样;但是第5~6行就让人匪夷所思了,X引用的42和Y引用的42本来是2个对象,应该输出False才对,不过,因为小的整数和字符串被缓存并复用了,所以is
告诉我们X和Y引用了同一个对象。
实际上,你可以用sys
模块中的getrefcount
函数查询对象的被引用次数。例如,我们查一下整数1被引用的次数:
>>> import sys
>>> sys.getrefcount(1)
812
这种对象缓存和复用的机制与代码是没有关系的。Python这样做是为了提高执行速度。
【End】
参考资料
《Python学习手册(第4版)》,机械工业出版社