如何交换python元组中的成员(a,b)=(b,a)在内部工作?

时间:2014-01-10 15:01:36

标签: python tuples python-internals iterable-unpacking

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的值在内部工作?它肯定不使用临时变量。

1 个答案:

答案 0 :(得分:65)

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

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

>>> 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 opcodes(将变量中的值推入堆栈)之后,堆栈顶部保持[a, b]ROT_TWO opcode交换堆栈中的前两个位置,因此堆栈现在位于顶部[b, a]。然后,两个STORE_FAST opcodes获取这两个值并将它们存储在赋值左侧的名称中。第一个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              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操作。

后者可能看起来像是一个浪费的操作,但是赋值的右侧可能是完全不同的函数,也许是生成元组的函数调用,因此Python解释器不做任何假设并始终使用UNPACK_SEQUENCE操作码。即使对于两个和三个名称的分配操作,它也会这样做,but a later (peephole) optimization step用上述BUILD_TUPLE和{{1}替换带有2或3个参数的UNPACK_SEQUENCE / ROT_TWO组合操作码的效率。