1

I ran into a code that calls a function f():

def f():
    return 1, []

a, [] = f()

I was wondering why didn't the author use a, _ = f(). Why is this syntax even allowed?

[] = []
a, [] = 444, []

Where can I read more about [] = [] philosophy?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Tom B.
  • 21
  • 3
  • 1
    This is very weird. It won't do anything and doing `a, _ = f()` is definitely the preferred way. I'd be very skeptical about the site you got this from. – Ted Klein Bergman Nov 28 '21 at 16:47
  • See https://stackoverflow.com/questions/5182573/multiple-assignment-semantics. You can assign to multiple elements on the left, which is usually done using a tuple: `a, b = [1, 2]`, which is the same as `(a, b) = [1, 2]`. You can also use the list syntax on the left: `[a, b] = [1, 2]`. Here, you are doing that with a list of 0 item on the left, and no item either on the right: `[] = []` is therefore valid. I don't see much usefulness for it, though... – Thierry Lathuille Nov 28 '21 at 17:05
  • 2
    ``[] = []`` is the degenerate case of ``[a, b, c] = [x, y, z]``. Usually it unpacks *some* values into *some* names, but it may also unpack no values into no names. – MisterMiyagi Nov 28 '21 at 17:05
  • Thanks for the answers. So basically, `[] = []` is less performant because an empty list has to be constructed. Correct? – Tom B. Nov 28 '21 at 17:10
  • @TomB. I think that unpacking is more normally done with tuples so `()` might be better than `[]` on the left hand side of the assignment. The difference compared to `_` is that it will raise an exception (`ValueError`) if the list is not empty, which might be a check that you want. – alani Nov 28 '21 at 17:13
  • @ThierryLathuille Re "I don't see much usefulness", please see my previous comment regarding the exception. The programmer might want to assert that the list is indeed empty, by unpacking it into zero variables. – alani Nov 28 '21 at 17:17

3 Answers3

4

Consider a less strange-looking example:

def f():
    return 1, [2, 3]

a, [b, c] = f()

This is an ordinary destructuring assignment; we simple assign a = 1, b = 2 and c = 3 simultaneously. The structure on the left-hand side matches that on the right-hand side (on the left-hand side, we can use [] and () interchangeably, and the corresponding list and tuple objects don't exist after the fact - or at any point; they're purely syntactical here).

The code you have shown is simply the degenerate case of this, where there are zero elements in the sub-sequence. The 1 is assigned to a, and all zero of the elements [] are assigned to zero target names.

I was wondering why not the author used a, _ = f()

I cannot read the author's mind, but one possible reason:

def f():
    return 1, g()

# later:
# precondition: the result from g() must be empty for code correctness
a, [] = f() # implicitly raises ValueError if the condition is not met

This is easy to write, but requires some explanation and is perhaps not the greatest approach. Explicit is better than implicit.

Why is this syntax even allowed

Because special cases aren't special enough to break the rules.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
1

The syntax [] = [] is an edge case of assignment to a target list: it unpacks values from an empty iterable into zero target names.


Python's assignment statement allows multiple assignment by using list or tuple syntax for the targets. The source can be any iterable with the correct number of items.

>>> a, b = range(2)
>>> a
0
>>> (a, b) = {1: "one", 2: "two"}
>>> a
1
>>> [a, b] = {3, 5}
>>> a
3

Notably, the left hand side does not denote an actual tuple/list in this case. It merely defines the structure in which the actual assignment targets a and b are, akin to pattern matching.

As an edge case, the syntax also allows specifying one or zero length assignment lists.

>>> # assign single-element iterable into single name
>>> [a] = {15}
>>> a
15
>>> # assign no-element iterable into no name
>>> [] = []

It is worth pointing out that the left and right hand side [] are fundamentally different things. The right hand side ... = [] denotes an actual list object with no elements. The left hand side [] = ... merely denotes "zero names".


Multiple assignment often serves the two-fold purpose of performing an actual assignment while checking the number of items.

>>> # accept any number of secondary items
>>> a, b = 42, [16, 72]
>>> # accept only one secondary item
>>> a, [b] = 42, [16, 72]
...
ValueError: too many values to unpack (expected 1)

By using an empty target list, one enforces that there is an iterable but that it is empty.

>>> # empty iterable: fine
>>> a, [] = 444, []
>>> # non-empty iterable: error
>>> a, [] = 444, ["oops"]
...
ValueError: too many values to unpack (expected 0)
MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
0

As already mentioned, this syntax is allowed due to the so-called "pattern-matching" in Python. In this particular case, the list is empty, so Python matches nothing with nothing. If you had more elements in the list, this would actually be helpful, as Karl Knechtel has shown in his answer.

Your example is just a special (and useless) case of pattern matching (for matching 0 elements).

  • This has nothing to do with pattern matching, which was only [added in python-3.10](https://docs.python.org/3/whatsnew/3.10.html). – ekhumoro Nov 28 '21 at 17:51