Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I assign to [], but not () in Python? [duplicate]

This came up in a recent PyCon talk.

The statement

[] = []

does nothing meaningful, but it does not throw an exception either. I have the feeling this must be due to unpacking rules. You can do tuple unpacking with lists too, e.g.,

[a, b] = [1, 2]

does what you would expect. As logical consequence, this also should work, when the number of elements to unpack is 0, which would explain why assigning to an empty list is valid. This theory is further supported by what happens when you try to assign a non-empty list to an empty list:

>>> [] = [1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack

I would be happy with this explanation, if the same would also be true for tuples. If we can unpack to a list with 0 elements, we should also be able to unpack to a tuple with 0 elements, no? However:

>>> () = ()
  File "<stdin>", line 1
SyntaxError: can't assign to ()

It seems like unpacking rules are not applied for tuples as they are for lists. I cannot think of any explanation for this inconsistency. Is there a reason for this behavior?

like image 672
j0ker Avatar asked Apr 25 '15 19:04

j0ker


People also ask

Why is copy () not working in Python?

The main reason why the list. copy() method may not work for you is because you assume that it creates a deep copy when, in reality, it only creates a shallow copy of the list.

Does assignment make a copy in Python?

Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other.

How do you copy an array in Python?

We can create a copy of an array by using the assignment operator (=). In Python, Assignment statements do not copy objects, they create bindings between a target and an object.


2 Answers

The comment by @user2357112 that this seems to be coincidence appears to be correct. The relevant part of the Python source code is in Python/ast.c:

switch (e->kind) {
    # several cases snipped
    case List_kind:
        e->v.List.ctx = ctx;
        s = e->v.List.elts;
        break;
    case Tuple_kind:
        if (asdl_seq_LEN(e->v.Tuple.elts))  {
            e->v.Tuple.ctx = ctx;
            s = e->v.Tuple.elts;
        }
        else {
            expr_name = "()";
        }
        break;
    # several more cases snipped
}
/* Check for error string set by switch */
if (expr_name) {
    char buf[300];
    PyOS_snprintf(buf, sizeof(buf),
                  "can't %s %s",
                  ctx == Store ? "assign to" : "delete",
                  expr_name);
    return ast_error(c, n, buf);
}

tuples have an explicit check that the length is not zero and raise an error when it is. lists do not have any such check, so there's no exception raised.

I don't see any particular reason for allowing assignment to an empty list when it is an error to assign to an empty tuple, but perhaps there's some special case that I'm not considering. I'd suggest that this is probably a (trivial) bug and that the behaviors should be the same for both types.

like image 113
Blckknght Avatar answered Oct 05 '22 20:10

Blckknght


I decided to try to use dis to figure out what's going on here, when I tripped over something curious:

>>> def foo():
...   [] = []
... 
>>> dis.dis(foo)
  2           0 BUILD_LIST               0
              3 UNPACK_SEQUENCE          0
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE        
>>> def bar():
...   () = ()
... 
  File "<stdin>", line 2
SyntaxError: can't assign to ()

Somehow the Python compiler special-cases an empty tuple on the LHS. This difference varies from the specification, which states:

Assignment of an object to a single target is recursively defined as follows.

...

  • If the target is a target list enclosed in parentheses or in square brackets: The object must be an iterable with the same number of items as there are targets in the target list, and its items are assigned, from left to right, to the corresponding targets.

So it looks like you've found a legitimate, although ultimately inconsequential, bug in CPython (2.7.8 and 3.4.1 tested).

IronPython 2.6.1 exhibits the same difference, but Jython 2.7b3+ has a stranger behavior, with () = () starting a statement with seemingly no way to end it.

like image 35
Ignacio Vazquez-Abrams Avatar answered Oct 05 '22 20:10

Ignacio Vazquez-Abrams