pythoniterable-unpackingmultiple-assignment

Don't understand this code showing tuple unpacking


Question: Could someone explain the output? Why is z equal to 2 in the second print()?

Code:

x=1
y=2
x,y,z=x,x,y
print(y,x,y,z)
z,y,z=x,y,z
print(x,y,z)

Output:

1 1 1 2
1 1 2

Solution

  • Python tuple assignment uses a stack, so assuming x=1 and y=2:

    x,y,z=x,x,y
    

    translates abstractly to:

    push x (value 1) on stack:    top_of_stack(TOS) -> 1
    push x (value 1) on stack:    TOS -> 1, 1
    push y (value 2) on stack:    TOS -> 2, 1, 1
    reverse top 3 stack entries:  TOS -> 1, 1, 2
    pop stack and store in x (x=1)  TOS -> 1, 2
    pop stack and store in y (y=1)  TOS -> 2
    pop stack and store in z (z=2)  TOS -> empty
    

    final result is x=1, y=1, z=2, next line:

    z,y,z=x,y,z
    

    pushes x,y,z (1,1,2), then loads z=1, then y=1, then z=2 (overwriting z=1).

    Here's the actual disassembly with comments:

    >>> dis.dis('x=1;y=2;x,y,z=x,x,y;z,y,z=x,y,z')
      1           0 LOAD_CONST               0 (1)     # load constant 1
                  2 STORE_NAME               0 (x)     # assign to x
                  4 LOAD_CONST               1 (2)     # load constant 2
                  6 STORE_NAME               1 (y)     # assign to y
                  8 LOAD_NAME                0 (x)     # push x on stack (1)
                 10 LOAD_NAME                0 (x)     # push x again on stack (1)
                 12 LOAD_NAME                1 (y)     # push y on stack (2)
                 14 ROT_THREE                          # two instructions reverse stack
                 16 ROT_TWO                            # .. 2,1,1 becomes 1,1,2
                 18 STORE_NAME               0 (x)     # pop x=1
                 20 STORE_NAME               1 (y)     # pop y=1
                 22 STORE_NAME               2 (z)     # pop z=2
                 24 LOAD_NAME                0 (x)     # push x (1)
                 26 LOAD_NAME                1 (y)     # push y (1)
                 28 LOAD_NAME                2 (z)     # push z (2)
                 30 ROT_THREE                          # reverse stack
                 32 ROT_TWO                            #  2,1,1 becomes 1,1,2
                 34 STORE_NAME               2 (z)     # pop z=1  <---
                 36 STORE_NAME               1 (y)     # pop y=1      \
                 38 STORE_NAME               2 (z)     # pop z=2 (overwriting previous)
    

    You can also think of this more abstractly as:

    1. Compute the right side values first.
    2. Assign left-to-right to variables on right.

    So:

    x=1
    y=2
    x,y,z=x,x,y
    

    Means:

    # x,y,z=1,1,2
    x=1
    y=1
    z=2
    

    Then:

    z,y,z=x,y,z
    

    Means:

    # z,y,z = 1,1,2
    z=1
    y=1
    z=2   # overwriting previous z=1