29

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
martineau
  • 119,623
  • 25
  • 170
  • 301
rafoo
  • 1,506
  • 10
  • 17
  • 13
    There is no difference at all. If you need more convincing, use the `dis` module to disassemble functions containing each version, and observe that their bytecode is identical. – jasonharper Oct 29 '20 at 13:05
  • the only sifference you would ever find is if you called type( thing ).__name__ – an inconspicuous semicolon Oct 29 '20 at 13:09
  • 2
    Not sure if it's a duplicate, definitely related: https://stackoverflow.com/questions/6967632/unpacking-extended-unpacking-and-nested-extended-unpacking – Tomerikoo Oct 29 '20 at 13:09
  • 3
    @reece There's nothing to call `type` on, though; the brackets are purely syntactic here. `[a, b] = f()` does not create a list of any kind here. – chepner Oct 29 '20 at 13:14
  • 2
    For this case, all are redundant. But parentheses can be useful for unpacking nested things: `a, (b, c) = [[0, 1], [2, 3]]` leads to `a = [0, 1]`, `b = 2`, `c = 3`. – Niklas Mertsch Oct 29 '20 at 13:18
  • @NiklasMertsch that's a good point! but again, `a, [b, c] = ...` will give the same result... – Tomerikoo Oct 29 '20 at 13:25
  • I think that's a different issue, though. (I debated removing it from "my" answer, but I did make it a community wiki, and it's not *wrong*, so... :) ) – chepner Oct 29 '20 at 13:36

2 Answers2

36

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.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Also, I *believe* the definition of the assignment operator `:=` precludes any tricks like `(x := [a,b]) = f()`. (That, at least, is definitely a syntax error.) – chepner Oct 29 '20 at 13:24
8

As the other answer has explained, there is no semantic difference. The only reason to prefer one form over another is visual clarity; x, y = f() is usually neater, but something like [(id,)] = do_sql_query() may sometimes be written to indicate that the result is expected to be a list containing a tuple. The assignment target [(id,)] is semantically equivalent to (id,), but it communicates something more to the human reader.

kaya3
  • 47,440
  • 4
  • 68
  • 97