本人刚刚开始学习 Python,对于 Python 中的诸多机制不甚理解。这是一位网友最近转给我的代码片段,令我很震惊。最近这个问题一直困扰我,为什么在 Python 中执行:
>>> a, b, c = b, c, a = c, b, a = 1, 2, 3
>>> a, b, c
输出会是
(3, 2, 1)
而不是 (1, 2, 3)
?
我首先使用 Python 的 dis 模块对代码的字节码进行了解析,得出了如下的字节码:
$ python -m dis ./main.py
1 0 LOAD_CONST 0 ((1, 2, 3))
2 DUP_TOP
4 UNPACK_SEQUENCE 3
6 STORE_NAME 0 (a)
8 STORE_NAME 1 (b)
10 STORE_NAME 2 (c)
12 DUP_TOP
14 UNPACK_SEQUENCE 3
16 STORE_NAME 1 (b)
18 STORE_NAME 2 (c)
20 STORE_NAME 0 (a)
22 UNPACK_SEQUENCE 3
24 STORE_NAME 2 (c)
26 STORE_NAME 1 (b)
28 STORE_NAME 0 (a)
2 30 LOAD_NAME 3 (print)
32 LOAD_NAME 0 (a)
34 LOAD_NAME 1 (b)
36 LOAD_NAME 2 (c)
38 CALL_FUNCTION 3
40 POP_TOP
42 LOAD_CONST 1 (None)
44 RETURN_VALUE
我平时只是用 Python 做数据分析,不是科班出身的程序员,所以没有学过编译原理之类的东西,也不懂栈什么的,所以只好硬着头皮理解这个。我讲一下我的理解:
为了理解这段字节码,我首先假设了一个更简单的情况:
a, b, c = 1, 2, 3
print(a, b, c)
此时解包取得的字节码为:
$ python -m dis ./demo.py
1 0 LOAD_CONST 0 ((1, 2, 3))
2 UNPACK_SEQUENCE 3
4 STORE_NAME 0 (a)
6 STORE_NAME 1 (b)
8 STORE_NAME 2 (c)
2 10 LOAD_NAME 3 (print)
12 LOAD_NAME 0 (a)
14 LOAD_NAME 1 (b)
16 LOAD_NAME 2 (c)
18 CALL_FUNCTION 3
20 POP_TOP
22 LOAD_CONST 1 (None)
24 RETURN_VALUE
按照我的理解,Python 对 (1, 2, 3)
的解包实际上是取得了栈:
[3, 2, 1]
之后依次从栈顶依次取出元素对 a,b 和 c = 进行了赋值,于是刚好输出 a, b, c
就有:1, 2, 3
。
而对于我们的例子而言:从Python字节码来看,最初状态下将 (1, 2, 3)
压进栈,栈状态为:
[(1, 2, 3)]
然后执行了一遍 DUP_TOP
复制了元组得到栈:
[(1, 2, 3), (1, 2, 3)]
对序列进行解包,得到栈状态:
[(1, 2, 3), 3, 2, 1]
进而从栈顶依次取值赋值给了 a,b 和 c,依次有:
a = 1
此时栈状态为:
[(1, 2, 3), 3, 2]
然后有:
b = 2
此时栈状态为:
[(1, 2, 3), 3]
再给 c
赋值就有:
c = 3
此时栈状态为:
[(1, 2, 3)]
又一次执行 DUP_TOP
得到:
[(1, 2, 3), (1, 2, 3)]
12 到 20 的操作大同小异,和上面基本差不多,此处略过。
但是到了最后一步 22 开始会发现 Python 没有再对栈顶的 (1, 2, 3)
进行复制而是直接解包,于是栈变为:
[3, 2, 1]
之后依次从栈顶依次取出元素对 c,b 和 a 进行了赋值,就有了输出结果为 3, 2, 1
所以也就是说,难道在 Python 中所有连锁链式赋值,Python 在将最右侧的常数元组压进栈之后就开始从左往右而不是从右往左处理了吗?而且最终结果都只会看最后一个吗?为什么 Python 是这样工作的?这很奇怪,我无法理解。
按文档所说,python的赋值顺序跟c语言是反的,从右往左赋值,没有你说的那么复杂.
https://docs.python.org/3/reference/simple_stmts.html#assignment-statements
An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.
所以
a, b, c = b, c, a = c, b, a = 1, 2, 3
#等同于
a, b, c = 1, 2, 3
b, c, a = 1, 2, 3
c, b, a = 1, 2, 3
能翻官方文档,这是真大佬啊……非常感谢您的回复,这个问题我理解了。
damn !我也摸不着头脑捏