37

I was surprised to find the following, in Python 3, the first two raise nothing:

>>> [] = ()
>>> () = ()
>>> {} = ()
  File "<stdin>", line 1
SyntaxError: can't assign to literal

In Python 2.7, only the first one raises nothing:

>>> [] = ()
>>> () = ()
  File "<stdin>", line 1
SyntaxError: can't assign to ()
>>> {} = ()
  File "<stdin>", line 1
SyntaxError: can't assign to literal

What is going on here? Why are any of then not raising errors? And why was the () = () presumably added to be valid in Python 3?

*Note, you can replace the right hand side with any empty iterable (e.g. [] = set()), I just choose an empty tuple for the illustration

Chris_Rands
  • 38,994
  • 14
  • 83
  • 119
  • I knew it was about unpacking. Then I though, in JavaScript this was certainly not possible (it’s called destructuring there). I was completely surprised that things like `let [] = [];` and `({}) => 1` are valid in JS. – Sebastian Simon Jan 25 '18 at 21:52
  • 1
    Great question, never realise this was possible until now – jamylak Feb 09 '18 at 17:57

4 Answers4

28

According to Issue23275, these are basically quirks causing no real harm but also no utility. Note that [] = () does not alter the list literal:

>>> [] = ()
>>> type([])
<class 'list'>

[] = x statements basically assert that x is iterable and that x is empty (although no-one would recommend using them this way), e.g.

>>> [] = (1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>> [] = (1,)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack

As John Y comments it is best to think of [] = () as not an assignment but a way of being consistent with Python's iterable unpacking syntax.

As ArrowCase comments, this syntax also extends to multiple assignments:

>>> a = [] = ()
>>> a
()

Looking at the CPython bytecode of the multiple assignment illustrates that this operations are similar to the normal iterable unpacking syntax, using the UNPACK_SEQUENCE instruction:

>>> dis.dis('a = [] = ()')
  1           0 BUILD_TUPLE              0
              2 DUP_TOP
              4 STORE_NAME               0 (a)
              6 UNPACK_SEQUENCE          0
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> dis.dis('[a, b] = (1, 2)')
  1           0 LOAD_CONST               3 ((1, 2))
              2 UNPACK_SEQUENCE          2
              4 STORE_NAME               0 (a)
              6 STORE_NAME               1 (b)
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

The same Issue23275 states that () = () was added as valid syntax to Python 3 for concordance. It was decided that removing [] = () would break code needlessly, since it causes no harm and fits with iterable unpacking logic. {} = () is still invalid because the unpacking syntax does not make sense in this context with braces.

In case anyone is wondering, syntax like list() = () is simply syntactically invalid, because you can never assign to function call.

Chris_Rands
  • 38,994
  • 14
  • 83
  • 119
  • 1
    It should not be surprising that you can't assign (or unpack) an empty tuple to a *dictionary*. – John Y Jan 25 '18 at 15:23
  • `{} = ()` doesn't work becuase you can't assign to a dict. You can only assign to a dict key. So, `{}[0] = ()` or even `{}[()] = ()` works since they assign to dict key. – Munir Jan 25 '18 at 15:27
  • 1
    @Munir So you're saying you can't assign to a dict because you can't assign to a dict? Not a very enlightening explanation... – Stefan Pochmann Jan 25 '18 at 15:28
  • 6
    @StefanPochmann - You can't *actually* assign to a tuple or list either. But those are sequences, and as such you can *unpack* (assign sequentially) to their **elements**. A dictionary isn't a sequence, and it doesn't have "elements" per se, it has key-value pairs. – John Y Jan 25 '18 at 15:32
  • 1
    @Munir But those do not do anything on the given iterable. – glglgl Jan 25 '18 at 15:39
  • @glglgl It's the same as doing `[] = ()` but inside an empty dict. I'm just saying that in case of a dict you need to use a key becuase the dict doesn't unpack like a tuple or list. – Munir Jan 25 '18 at 15:43
  • 3
    @Munir No it's not the same. You can do `{}[0] = (1, 2)` but not `[] = (1, 2)`. – Stefan Pochmann Jan 25 '18 at 15:47
  • @Munir Imagine you have a generator function which has side effects, such as `def f(): yield 123; print("Side effect"); yield 321`. Then there is a big difference between `a,b = f()` and `{}[0] = f()`. The same holds for an empty iterable/iterator/generator: `def f(): print("Side effect"); return; yield 321` where `() = f()` prints `Side effect` while `{}[0] = f()` doesn't. – glglgl Jan 25 '18 at 15:55
  • @StefanPochmann I don't understand what you are trying to say. The answer says "{} = () is still invalid for reasons not entirely clear to me" and I am saying it is invalid because you can't assign directly to dict, only to a dict key. When you do `{}[0] = (1,2)`, you are assinging to the key [0], which is completely valid. When you do [] = (1,2), you are trying to assign to undefined values since the list is empty and of course it doesn't work. – Munir Jan 25 '18 at 15:56
  • @glglgl See my comment above. I am not talking about different scenarios of assingment. I am trying to explain one particular line in the answer. – Munir Jan 25 '18 at 15:58
  • @Munir Even these are not equivalent on execution, just on result. In one case, the unpacking of the right hand side operator is performed (which admittedly is a kind of no-op for an empty tuple), in the other case not. So from the observable side of the action, you are indeed right, they are equivalent as they both do nothing. But concerning what happens internally, it makes a difference. – glglgl Jan 25 '18 at 16:02
  • 1
    @glglgl Maybe me choice of words is not correct, but I didn't mean to say they do the same internally. I was just trying to show how you can do blank operations with a dict. – Munir Jan 25 '18 at 16:04
  • @JohnY Thank you, great comment, I've updated the answer a bit to try and reflect what you said – Chris_Rands Jan 25 '18 at 20:42
  • 1
    Your justification for claiming "`[] = ()` does not change `[]`" is just testing the type of list literal, which obviously will not change. However, as explained in @glglgl's answer, this syntax exists so that variables specified in the LHS collection can be assigned with values from the RHS iterable, so in that sense, the syntax is meant to "change" the list/tuple. Additionally, you can use multiple assignment to see that the evaluated value of this "assignment from iterable" expression is its own RHS: `a = [] = ()` assigns `()` to `a`. – ArrowCase Jan 25 '18 at 21:04
  • 1
    @ArrowCase Thanks, yes, agreed, and I've updated the text to make it clearer, although I don't think it's necessarily obvious a priori that the list literal would not change – Chris_Rands Jan 25 '18 at 21:07
  • Why in the world is `() = x` even legal syntax when we already have `[] = x`? – user541686 Jan 26 '18 at 01:04
  • @ArrowCase On reflection, I added a note to the answer about the multiple assignment case, because I think that's quite interesting, thanks again for your comment – Chris_Rands Feb 09 '18 at 18:48
26

There is a way to assign variables from an iterable:

>>> a, b = iter((1, 2))
>>> a
1
>>> b
2
>>> [c, d] = iter((4, 5))
>>> c
4
>>> d
5

The [] = … and () = … assignments seem to be special cases of these.

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • 6
    You don't actually have to say `iter(...)`. You can simply do `a, b = 1, 2` – DeepSpace Jan 25 '18 at 15:21
  • 6
    @DeepSpace There's no way glglgl doesn't know that. Looks intentional to me, to somewhat "anonymize" the tuple, to emphasize that it can be any iterable. – Stefan Pochmann Jan 25 '18 at 15:23
  • @StefanPochmann Right. First I wanted to do something with `itertools` and looked for something like `limit` or so, but an arbitrary iterator yielding the right number of items seemed ok for me then. – glglgl Jan 25 '18 at 15:25
  • This should be the accepted answer. This behavior also explains why `[] = {}` and `() = {}` are possible, but not the reverse. The values in the dictionary can be iterated and assigned to the variables in the list/tuple, but it can't be reversed because the dictionary can't be used to hold a "list of variables" to assign to like the other two. – ArrowCase Jan 25 '18 at 20:55
  • Thanks, this is a nice way of thinking about it, put another way [a, b] = range(2) and [] = range(0) as noted in the issue – Chris_Rands Jan 25 '18 at 21:36
  • I think it's the opposite of special case, the all assignments follow the same rule. – mike3996 Jan 26 '18 at 06:54
  • 6
    It would be better to refer to the cases in question as degenerate rather than special. They arise from consistent application of behavior. – CAD97 Jan 26 '18 at 07:07
9

The left hand side of an assignment statement is not an expression, it is a target list. Short summary:

  • If the target list is an identifier, the name is just bound to the right hand side.
  • If the target list is a comma seperated list of targets, the right hand side is unpacked and the unpacked elements are assigned to the listed targets.
  • A target list may be enclosed in parentheses or square brackets. In particular, that allows creating empty target lists, as seen in your examples.

This explains why [] and () are valid left hand sides for assignments: they are valid target lists. However, {} is not, as it is not a valid target list.

Of course, {} might be part of a target, for example as the primary of a subscription: {}[()] = 0 is valid python (but completely useless, of course).

user2357112
  • 260,549
  • 28
  • 431
  • 505
Reinstate Monica
  • 4,568
  • 1
  • 24
  • 35
7

This is syntax to unpack a two-element iterable into two assignment targets:

[x, y] = whatever

This generalizes up to three or more targets, but it also generalizes down:

[x] = whatever

unpacks a one-element iterable into one assignment target, and

[] = whatever

unpacks a zero-element iterable into zero assignment targets (which does nothing if whatever is a zero-element iterable, and throws an exception if it's not).

() = whatever also unpacks a zero-element iterable, but {} = whatever does not; there is no unpacking assignment syntax that involves braces.

user2357112
  • 260,549
  • 28
  • 431
  • 505