共享引用
到目前为止,已经学习了当单个变量被分配对象引用时会发生什么。现在向交互会话引入另一个变量然后观察对它的名称和对象会发生什么:
>>> a = 3
>>> b = a
键入这两个语句会产生图 6-2 中描绘的情形。第二个命令让 Python 创建了变量 b
;变量 a
正在被使用且这里没有被赋值,所以它被其引用的对象(3)所代替,然后 b
被设置为引用那个对象。最终结果是变量 a
和 b
最后引用同样的对象(也就是说,指向同样的内存块)。
图 6-2. 在接下来运行赋值 b =a 后的名称和对象。变量 b 变成了对象 3 的引用。在内部,变量事实上是运行字面量表达式 3 所创建的对象内存空间的指针。
在 Python 中这个场景 —— 多个名称引用同一个对象 —— 通常被称为共享引用(且有时只是共享对象)。注意当这个场景发生时,名称 a 和 b 并不直接相互连接;事实上,在 Python 中在任何时候都没有办法将一个变量连接到另一个变量。相反,两个变量通过它们的引用指向同样的对象。
接下来,假设再用一个语句来扩展这个会话:
>>> a = 3
>>> b = a
>>> a = 'spam'
和所有 Python 赋值一样,这个语句简单地创建了一个代表字符串值'spam' 的新对象且设置 a 来引用这个新对象。然而,它没有改变 b 的值;b 仍然引用原来的对象(整数 3)。最终引用结果如图 6-3 中所示。
图 6-3. 在最后运行赋值 a = 'spam' 后的名称和对象。变量 a 引用运行字面量表达式'spam' 所创建的新对象(也就是,内存片段),但变量 b 仍然引用原来的对象 3。因为这个赋值不是对象 3 的就地更改,它只改变了变量 a,而没有改变 b。
如果将 b 改为 spam
,同样类型的事情也将发生 —— 赋值将只改变 b,而非 a。如果完全没有类型差异,这个行为也会发生。比如,考虑下面三个语句:
>>> a = 3
>>> b = a
>>> a = a + 2
在这个序列中,同样的事情发生了。如图 6-2,Python 让变量 a 引用对象 3,让 b 引用和 a 一样的对象;和以前一样,最后的赋值将 a 设置为一个完全不同的对象(在本例中,是整数 5,+ 表达式的结果)。它没有顺带改变 b。事实上,在任何时候都没有办法覆盖对象 3 的值 —— 如第 4 章中介绍的,整数不可变,所以决不能被就地修改。
思考这个场景的一种方式是不像其他语言,在 Python 中变量总是对象指针,而非可改变内存区域的标签:对一个变量设置一个新值不会改变原来的对象,而是会让这个变量去引用一个完全不同的对象。最终结果是对一个变量本身的赋值只能影响正在被赋值的这个单变量。然而,当可变对象和就地修改进入这个场景时,情况会在某种程度上改变;要看是怎么回事,继续前进。