a question about list as an element in a tuple

Chris Angelico rosuav at gmail.com
Mon Dec 16 04:57:11 CET 2013


On Mon, Dec 16, 2013 at 2:30 PM, liuerfire Wang <liuerfire at gmail.com> wrote:
> TypeError: 'tuple' object does not support item assignment
>
> In [5]: a
> Out[5]: ([1, 1], [])
>
> no problem, there is an exception. But a is still changed.
>
> is this a bug, or could anyone explain it?

It's not a bug, but it's a bit confusing. Here's what happens. The
augmented assignment operator += triggers an in-place addition (where
possible; for lists, it's possible), so the original list will be
changed:

>>> a = [1]
>>> b = a
>>> a += [2]
>>> b
[1, 2]

Whereas using separate assignment and addition creates a new list:

>>> a = a + [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]

To handle both mutable types (like list) and immutable ones (like
str), the augmented assignment operators have to be able to choose
whether or not they change their object:

>>> a = "1"
>>> b = a
>>> a += "2"
>>> b
'1'
>>> a
'12'

So what happens under the hood is, more or less:

a += [2]
# ... is equivalent to ...
a = a.__iadd__([2])

Which can be explored interactively:

>>> a = [1]
>>> a.__iadd__([2])
[1, 2]
>>> a
[1, 2]

The __iadd__ function ("in-place add") returns the new result, and in
the case of the list, that's done by changing the list. The assignment
is then done, which does nothing here, but with strings, is the
critical part of the job. So far, so good.

Now, when that result gets assigned to the tuple, we have a problem.
Tuples are immutable - can't change length (no appending), and can't
assign to elements. So when the final step happens, an exception is
thrown. At that point, the tuple is complaining "Hey, you can't assign
to me!", but the list has already done the appending, and it's too
late to undo that. So the list has changed, and when you go look at
it, you see the change (since the list is the same whether you find it
from the tuple or somewhere else), which creates the confusing
situation of throwing an exception after doing exactly what you
wanted.

Python exceptions aren't like SQL errors, causing a rollback of the
whole transaction. Or, more generally, Python expressions and
statements aren't transactional. So it's entirely possible, if a
little confusing, for part of a job to happen. Let's take another
example that has side effects:

tup = (1,2,3)
tup[2] = input("Enter something: ")   # use raw_input() in Python 2

Tuple assignment can't work... but it's fairly clear that this
_should_ still show the prompt and wait for the user to type
something, and only throw the exception later on. It's not as obvious
in the augmented assignment example, but both are actually doing the
same thing.

Hope that helps!

ChrisA



More information about the Python-list mailing list