元组 (a,b)=(b,a) 中的成员交换如何在内部工作?
问题描述
In [55]: a = 5
In [56]: b = 6
In [57]: (a, b) = (b, a)
In [58]: a
Out[58]: 6
In [59]: b
Out[59]: 5
这种 a 和 b 值的交换如何在内部进行?它绝对没有使用临时变量.
How does this swapping of values of a and b work internally? Its definitely not using a temp variable.
解决方案
Python 将右侧表达式与左侧赋值分开.首先评估右侧,并将结果存储在堆栈中,然后使用再次从堆栈获取值的操作码分配左侧名称.
Python separates the right-hand side expression from the left-hand side assignment. First the right-hand side is evaluated, and the result is stored on the stack, and then the left-hand side names are assigned using opcodes that take values from the stack again.
对于 2 或 3 项的元组赋值,Python 直接使用栈:
For tuple assignments with 2 or 3 items, Python just uses the stack directly:
>>> import dis
>>> def foo(a, b):
... a, b = b, a
...
>>> dis.dis(foo)
2 0 LOAD_FAST 1 (b)
3 LOAD_FAST 0 (a)
6 ROT_TWO
7 STORE_FAST 0 (a)
10 STORE_FAST 1 (b)
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
在两个 LOAD_FAST
操作码之后(将变量中的值压入堆栈),堆栈顶部保存 [a, b]
.ROT_TWO
opcode 交换前两个位置在堆栈上,所以堆栈现在在顶部有 [b, a]
.这两个 STORE_FAST
操作码 然后采用这两个值并将它们存储在赋值左侧的名称中.第一个 STORE_FAST
弹出堆栈顶部的值并将其放入 a
,下一个再次弹出,将值存储在 b
中.需要旋转是因为 Python 保证左侧目标列表中的分配是从左到右完成的.
After the two LOAD_FAST
opcodes (which push a value from a variable onto the stack), the top of stack holds [a, b]
. The ROT_TWO
opcode swaps the top two positions on the stack so the stack now has [b, a]
at the top. The two STORE_FAST
opcodes then takes those two values and store them in the names on the left-hand side of the assignment. The first STORE_FAST
pops a value of the top of the stack and puts it into a
, the next pops again, storing the value in b
. The rotation is needed because Python guarantees that assignments in a target list on the left-hand side are done from left to right.
对于 3 名分配,ROT_THREE
<执行/a> 后跟 ROT_TWO
以反转堆栈上的前三项.
For a 3-name assignment, ROT_THREE
followed by ROT_TWO
is executed to reverse the top three items on the stack.
对于较长的左侧赋值,会构建一个显式元组:
For longer left-hand-side assignments, an explicit tuple is built:
>>> def bar(a, b, c, d):
... d, c, b, a = a, b, c, d
...
>>> dis.dis(bar)
2 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 LOAD_FAST 2 (c)
9 LOAD_FAST 3 (d)
12 BUILD_TUPLE 4
15 UNPACK_SEQUENCE 4
18 STORE_FAST 3 (d)
21 STORE_FAST 2 (c)
24 STORE_FAST 1 (b)
27 STORE_FAST 0 (a)
30 LOAD_CONST 0 (None)
33 RETURN_VALUE
这里使用 [d, c, b, a]
的堆栈来构建一个元组(以相反的顺序,BUILD_TUPLE
再次从堆栈中弹出,将结果元组推入堆栈),然后 UNPACK_SEQUENCE
再次从堆栈中弹出元组,将所有元素推回元组再次返回堆栈以进行 STORE_FAST
操作.
Here the stack with [d, c, b, a]
is used to build a tuple (in reverse order, BUILD_TUPLE
pops from the stack again, pushing the resulting tuple onto the stack), and then UNPACK_SEQUENCE
pops the tuple from the stack again, pushes all elements back from the tuple back onto the stack again for the STORE_FAST
operations.
后者可能看起来是一种浪费的操作,但赋值的右侧可能是完全不同的东西,一个产生元组的函数调用,所以 Python 解释器不做任何假设并始终使用 UNPACK_SEQUENCE
操作码.即使对于两个和三个名称分配操作,它也是如此,但稍后(窥视孔)优化步骤将 BUILD_TUPLE
/UNPACK_SEQUENCE
组合替换为具有上述 ROT_TWO
的 2 或 3 个参数,并且ROT_THREE
操作码提高效率.
The latter may seem like a wasteful operation, but the right-hand side of an assignment may be something entirely different, a function call that produces a tuple perhaps, so the Python interpreter makes no assumptions and uses the UNPACK_SEQUENCE
opcode always. It does so even for the two and three-name assignment operations, but a later (peephole) optimization step replaces a BUILD_TUPLE
/ UNPACK_SEQUENCE
combination with 2 or 3 arguments with the above ROT_TWO
and ROT_THREE
opcodes for efficiency.
相关文章