How does swapping of members in tuples (a,b)=(b,a) work internally?

iterable-unpacking, python, python-internals, tuples

Solution

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.

Problem

``` 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.

Original source