- 原文链接: https://chowyi.com//key-points-of-chained-assignment-in-python/
- 版权声明: 文章采用 CC BY-NC-SA 4.0 协议进行授权,转载请注明出处!
背景
写 Python 四年有余了,常见的坑和奇淫巧技也都知道一些。今天在 Python 连续赋值上遇到了一个新的知识点,学习记录一下。
Python 连续赋值容易导致错误的情况有两个知识点:
- 相同的引用
- 赋值的顺序(本次探讨的重点)
相同的引用
这个有点 Python 经验的同学都知道或者很好理解,对可变对象的赋值,其实赋的是可变对象的引用。
在下面的代码中,因为['hello']
是一个列表,即可变对象,变量a
和b
都是这个同一个列表的引用。因此,对b
修改时,a
的值也变了,因为它们本来就指向同一个对象。
1 | s1 = s2 = 'hello' |
赋值的顺序
通常我们并不是太关心在使用连续赋值过程中赋值的顺序,就像下面这两行代码,赋值的先后次序并不影响结果。
1 | s1 = s2 = 'hello' |
但到底是:
- 是先把
'hello'
赋值给s2
,然后把s2
赋值给s1
吗? - 还是把
'hello'
赋值给s2
,然后'hello'
赋值给s1
呢? - 又或者是把
'hello'
赋值给s1
,然后'hello'
赋值给s2
呢?
在下面这个例子中让我犯了难。
1 | class ListNode: |
Output:
1->1
1
我本期望赋值操作可以从右向左进行赋值,让上面代码中的变量a
和b
有相同的结果。即:
1 | a = a.next = ListNode(1) |
我期望上面的语句等同于
1 | a.next = ListNode(1) |
事与愿违,解释器总是很忠实的按照原有的设计去执行代码。那么它到底是怎么做的呢?
我们可以import dis
,把 python 代码反汇编来看一下。
1 | import dis |
下面看看 Line 14, 18, 19 翻译过来的汇编码:
汇编语言我并不了解,下面的注释是我根据命令名称的猜测,如有错误恳请指出。
1 | ... |
由汇编码就很容易看出,在 Python 中类似a = b = 'hello'
的连续赋值过程,实际上是将最右侧的常量或变量,对其左侧=
前的各变量从左至右依次赋值。
即:
1 | a = b = 'hello' |
等同于
1 | a = 'hello' |
回到上面让我犯难的例子中:
1 | a = a.next = ListNode(1) |
等同于
1 | _ = ListNode(1) |
而不是
1 | a.next = ListNode(1) |
了解了原理,要想实现我期望的效果,其实只要调换一下顺序就行,像下面这样:
1 | a.next = a = ListNode(1) |
不过这样降低了可读性,不利于理解,最好还是不要这样写!
总结
- 这种错误隐秘难调,最好不要使用连续赋值(Chained Assignment)。
- 对基础知识点要加强学习。
- 连续赋值在其他编程语言中似乎不是这样的,Python 是个特例,这点有待考证!!!
- 原文链接: https://chowyi.com//key-points-of-chained-assignment-in-python/
- 版权声明: 文章采用 CC BY-NC-SA 4.0 协议进行授权,转载请注明出处!