Search code examples
pythoniterable-unpacking

Unpacking: [x,y], (x,y), x,y - what is the difference?


What is the difference in Python between unpacking a function call with [], with () or with nothing?

def f():
    return 0, 1

a, b = f() # 1
[a, b] = f() # 2
(a, b) = f() # 3

Solution

  • There is no difference. Regardless of what kind of syntactic sequence you use, the same byte code is generated.

    >>> def f():
    ...   return 0, 1
    ...
    >>> import dis
    >>> dis.dis('[a,b] = f()')
      1           0 LOAD_NAME                0 (f)
                  2 CALL_FUNCTION            0
                  4 UNPACK_SEQUENCE          2
                  6 STORE_NAME               1 (a)
                  8 STORE_NAME               2 (b)
                 10 LOAD_CONST               0 (None)
                 12 RETURN_VALUE
    >>> dis.dis('(a,b) = f()')
      1           0 LOAD_NAME                0 (f)
                  2 CALL_FUNCTION            0
                  4 UNPACK_SEQUENCE          2
                  6 STORE_NAME               1 (a)
                  8 STORE_NAME               2 (b)
                 10 LOAD_CONST               0 (None)
                 12 RETURN_VALUE
    >>> dis.dis('a, b = f()')
      1           0 LOAD_NAME                0 (f)
                  2 CALL_FUNCTION            0
                  4 UNPACK_SEQUENCE          2
                  6 STORE_NAME               1 (a)
                  8 STORE_NAME               2 (b)
                 10 LOAD_CONST               0 (None)
                 12 RETURN_VALUE
    

    In every case, you simply call f, then use UNPACK_SEQUENCE to produce the values to assign to a and b.


    Even if you want to argue that byte code is an implementation detail of CPython, the definition of a chained assignment is not. Given

    x = [a, b] = f()
    

    the meaning is the same as

    tmp = f()
    x = tmp
    [a, b] = tmp
    

    x is assigned the result of f() (a tuple), not the "list" [a, b].


    Finally, here is the grammar for an assignment statement:

    assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)
    target_list     ::=  target ("," target)* [","]
    target          ::=  identifier
                         | "(" [target_list] ")"
                         | "[" [target_list] "]"
                         | attributeref
                         | subscription
                         | slicing
                         | "*" target
    

    Arguably, the "[" [target_list] "]" could and should have been removed in Python 3. Such a breaking change would be difficult to implement now, given the stated preference to avoid any future changes to Python on the scale of the 2-to-3 transition.