元组 (a,b)=(b,a) 中成员的交换如何在内部工作? [英] How does swapping of members in tuples (a,b)=(b,a) work internally?

查看:76
本文介绍了元组 (a,b)=(b,a) 中成员的交换如何在内部工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在[55]中:a = 5在 [56] 中:b = 6在[57]中:(a, b) = (b, a)在 [58] 中:一个出[58]:6在 [59] 中:b出[59]:5

这种 a 和 b 值的交换在内部是如何工作的?它绝对没有使用临时变量.

解决方案

Python 将右侧表达式与左侧赋值分开.首先评估右侧,并将结果存储在堆栈中,然后使用操作码分配左侧名称,这些操作码再次来自堆栈中的值.

对于 2 或 3 项的元组赋值,Python 只是直接使用堆栈:

<预><代码>>>>导入文件>>>def foo(a, b):... a, b = b, a...>>>dis.dis(foo)2 0 LOAD_FAST 1 (b)3 LOAD_FAST 0 (a)6 ROT_TWO7 STORE_FAST 0 (a)10 STORE_FAST 1 (b)13 LOAD_CONST 0 (无)16 RETURN_VALUE

在两个 LOAD_FAST 操作码之后(将变量中的值压入堆栈),堆栈顶部保存 [a, b].ROT_TWO 操作码 交换前两个位置在堆栈上,所以堆栈现在在顶部有 [b, a] .两个 STORE_FAST 操作码 然后采用这两个值并将它们存储在赋值左侧的名称中.第一个 STORE_FAST 弹出栈顶的一个值并将其放入 a,下一个再次弹出,将值存储在 b 中.需要旋转是因为 Python 保证左侧目标列表中的赋值是从左到右完成的.

对于 3 名称分配,ROT_THREE 后跟 ROT_TWO 被执行以反转堆栈中的前三项.

对于较长的左侧赋值,构建了一个显式元组:

<预><代码>>>>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 415 解包_序列 418 STORE_FAST 3 (d)21 STORE_FAST 2 (c)24 STORE_FAST 1 (b)27 STORE_FAST 0 (一)30 LOAD_CONST 0(无)33 RETURN_VALUE

这里用[d, c, b, a]的栈来构建元组(以相反的顺序,BUILD_TUPLE 再次从堆栈中弹出,将生成的元组推入堆栈),然后 UNPACK_SEQUENCE 再次从堆栈中弹出元组,将所有元素推回元组再次返回堆栈以进行 STORE_FAST 操作.

后者似乎是一种浪费的操作,但赋值的右侧可能完全不同,一个函数调用可能产生一个元组,所以 Python 解释器不做任何假设并始终使用 UNPACK_SEQUENCE 操作码.它甚至对于两个和三个名称赋值操作也是如此,但是后来的(窥视孔)优化步骤用上述 ROT_TWO 和 2 或 3 个参数替换了 BUILD_TUPLE/UNPACK_SEQUENCE 组合和ROT_THREE 操作码以提高效率.

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

How does this swapping of values of a and b work internally? Its definitely not using a temp variable.

解决方案

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.

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        

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.

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        

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.

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.

这篇关于元组 (a,b)=(b,a) 中成员的交换如何在内部工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆