一个关于 += 的谜题
Leonardo Rochael 在 2013 年的 Python 巴西会议上提到了这个谜题:
1 | 1, 2, [30, 40]) t = ( |
以上两个表达式到底会发生下面 4 种情况下的哪一种?
刚看到这个问题的时候很多人可能会选择 2,但其实答案是 4。我们用控制台运行这段代码得到下面的结果:
1 | 1, 2, [30, 40]) t = ( |
变量不是盒子
在回答这个问题之前我们来了解 Python 中的变量。
“变量是盒子”这样的比喻经常被使用,但是这有碍于理解面向对象语言中的引入式变量。如下所示的交互式控制台中,无法使用“变量是盒子”做解释。
1 | 1, 2, 3] a = [ |
如果把变量想象为盒子,那么无法解释 Python 中的赋值。Python 变量类似于 Java 中的引用式变量,因此最好把它们理解为附加在对象上的标注。
为了理解 Python 中的赋值语句,应该始终先读右边(对引用式变量来说,说把变量分配给对象更合理。毕竟,对象在赋值之前就创建了。)。对象在右边创建或获取,在此之后左边的变量才会绑定到对象上,这就像为对象贴上标注。
元组不可变性指的是?
元组与多数 Python 集合(列表、字典、集,等等)一样,保存的是对象的引用。如果引用的元素是可变的,及时元组本身不可变,元素依然可变。也就是说,元组的不可变性其实指 tuple 数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。
我们看一下示例:
1 | 1, 2, [30, 40]) t = ( |
查看 t[-1] 列表的标识可以看到标识没变,只是值变了。
回到最初的问题,如果写成如下代码:
1 | 1, 2, [30, 40]) t = ( |
我们发现就可以避免异常。
因此,引发异常的操作在于对不可变元组的元素进行了赋值。这也对前面所说的变量是标注作了很好的注释。在分配给变量前对象就已经创建,所以尽管后来的赋值操作引发了异常,列表对象还是发生了变化。
Note
- 尽量不要把可变对象放在元组里面。
- 增量赋值(+=)不是一个原子操作。我们刚才看到了,它虽然抛出了异常,但还是完成了操作。