Does multiple assignment in Python use tuples?

Asked

Viewed 374 times

11

In the question Inverting two variables without using a temporary an answer quotes a link that comments on the multiple assignment of variables in Python:

Functioning of Multiple Assignment

The multiple assignment is the assignment of a tuple to another, where the values of the tuple to the right of the assignment signal, will be assigned, for the respective variables, in the double of the left side of the assignment signal. [sic]

In other references I have seen commenting similar, that the multiple assignment uses the deconstruction of tuples. I myself started from this premise to answer Why divide this operation into two cause result change?

In order to validate this information I went to analyze the bytecode:

from dis import dis

code = '''
a = 1
b = 3

a, b = b, a
'''

print(dis(code))

Who had a way out:

  2           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (a)

  3           4 LOAD_CONST               1 (3)
              6 STORE_NAME               1 (b)

  5           8 LOAD_NAME                1 (b)
             10 LOAD_NAME                0 (a)
             12 ROT_TWO
             14 STORE_NAME               0 (a)
             16 STORE_NAME               1 (b)
             18 LOAD_CONST               2 (None)
             20 RETURN_VALUE
None

That is, it loads in the stack the value of b, stack the value of a, exchange the two values inside the stack and make two assignments, first in a, then in b. There is no construction and deconstruction of tuples in the process, such as if I actually define a tuple with the two values:

from dis import dis

code = '''
a = 1
b = 3

c = b, a
'''

print(dis(code))

Which generates the following sequence of operations:

  2           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (a)

  3           4 LOAD_CONST               1 (3)
              6 STORE_NAME               1 (b)

  5           8 LOAD_NAME                1 (b)
             10 LOAD_NAME                0 (a)
             12 BUILD_TUPLE              2
             14 STORE_NAME               2 (c)
             16 LOAD_CONST               2 (None)
             18 RETURN_VALUE
None

Where we can clearly see the operation BUILD_TUPLE being executed before allocation.

After all, does multiple assignment use tuples in the process? If not, would it be a tuple-like assigment, only to illustrate which value is assigned to each variable?

  • Excellent question, accompanying....

  • In this Link, mainly in this excerpt "Multiple assignment is often called "tuple unpacking" because it’s Frequently used with tuples. But we can use Multiple assignment with any iterable, not just tuples. Here we’re using it with a list:".

  • @Luizaugusto It would be another example that might be wrong. A tuple unpacking that he cites would assign a tuple that already exists on the stack to variables. It would be like having c = (1, 3) and then do a, b = c; here yes there is the deconstruction of tuple (tuple unpacking), evident by the operation UNPACK_SEQUENCE. Multiple assignment does not occur.

  • See that he also quotes "What’s happening at a Lower level is that we’re Creating a tuple of...", contrary to the bytecode operations I posed in the question.

  • Correct, and against himself. I debugged the code, which he mentions in the link, x,y=10,20 . , I don’t have much experience with debug, but I haven’t found any build_tuple or build_list Is there any tuple construction or list in this code?

  • @Luizaugusto In this case happens to UNPACK_SEQUENCE for the value 10, 20 It’s a constant sequence. Instead of stacking two constants, Python already stacks the sequence with the two values and, for the assignment, deconstructs it.

Show 1 more comment

1 answer

8


Yes - in the language specification, and to understand the syntax, it is understood that "a tuple is constructed, its elements consumed as an iterator in attribution, and the tuple, being without references, is displaced".

What you see in disassemble indicates that this Pattern has been optimized, so that an exchange-only assignment is done directly, without the intermediate tuple. This is an optimization of the cPython implementation.

I repeated your test here for the situation with 3 variables - also there is the construction of a tuple - with 4 variables, disassemble is already:

In [6]: dis.dis(a)                                                                                                                                     
  1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (a)

  2           4 LOAD_CONST               1 (2)
              6 STORE_NAME               1 (b)

  3           8 LOAD_CONST               2 (3)
             10 STORE_NAME               2 (c)

  4          12 LOAD_CONST               3 (4)
             14 STORE_NAME               3 (d)

  5          16 LOAD_NAME                0 (a)
             18 LOAD_NAME                1 (b)
             20 LOAD_NAME                2 (c)
             22 LOAD_NAME                3 (d)
             24 BUILD_TUPLE              4
             26 UNPACK_SEQUENCE          4
             28 STORE_NAME               2 (c)
             30 STORE_NAME               3 (d)
             32 STORE_NAME               0 (a)
             34 STORE_NAME               1 (b)
             36 LOAD_CONST               4 (None)
             38 RETURN_VALUE

And then you can see the BUILD_TUPLE followed by UNPACK_SEQUENCE. That is, the optimization only avoids the construction of a tuple, followed by its "immediate deconstruction" with the "UNPACK_SEQUENCE" in these cases.

But really cool that you investigated this - I assumed that the tuple was built in all cases, and I used the syntax b, a = a, b with a certain weight on the heart due to the waste, however small, of resources. We have now seen that waste does not happen in cPython. (I just checked on Pypy3, the optimization also exists there)


This is what happens to tuples, built as literals, in the assignment itself.

It is important to remember that the "Sequence unpacking" works in several other ways also for any sequence or iterable on the right side, and is much more generic than the case of "exchange of two variables".

In particular, they’re worth things like:

In [4]: a, *b, c = range(10)                                                                                                                           

In [5]: b                                                                                                                                              
Out[5]: [1, 2, 3, 4, 5, 6, 7, 8]

In this case, the language counted "1 element before the element with asterisk, and 1 element at the end" and all others (8 in the case) are assigned, as a list, to the variable with "*". (And in this case, no tuple is created - separate opcodes are used to indicate how the iterable element that returned in the function call will be distributed at the time of assignment)

Another cool thing is that in newer versions of Python, "*" can be used to unzip a sequence when defining another - so you can do:

In [6]: a, b, c, d = 10, *range(2), 20                                                                                                                 

In [7]: a, b, c, d                                                                                                                                     
Out[7]: (10, 0, 1, 20)

In to

  • Well thought out the tests with more variables, I had not considered this.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.