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.