17

Why does the following throw an exception, although it succeeds?

>>> t = ([1, 2, 3], 4)
>>> t[0] += [1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
([1, 2, 3, 1], 4)
>>> 
Nehal J Wani
  • 16,071
  • 3
  • 64
  • 89
  • 9
    [Why does `a_tuple[i] += [‘item’]` raise an exception when the addition works](https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works) – Ashwini Chaudhary Jul 13 '16 at 06:41

3 Answers3

17

Found the answer on IRC.

t[0] += [1] is several discrete actions:

  1. loading t[0]
  2. building a new list with 1 in it
  3. adding that [1] to whatever t[0] is
  4. reassigning t[0]

It seems that x += y is basically x = x + y (but, is it?)

The tricky bit is that += implies assignment to both the tuple t and to the list t[0]

t[0] += [1] is not literally t[0] = t[0] + [1], it is: t[0] = t[0].__iadd__([1])

What really happens is:

  1. __iadd__ both mutates the list and returns it. So the list (which is the first element in t) has already got 1 appended to it.
  2. tuple's mutation is attempted in-place as well, but tuples are immutable, resulting in the exception.

Why is this not visible in plain sight? Because a n00b like me would expect t[0] += [1] to either succeed all together or fail, because it's one short line of python. But that's not always the case.

Nehal J Wani
  • 16,071
  • 3
  • 64
  • 89
  • 2
    This is a good question and a good answer. +1 from me. Also, for more background on `a += b` vs `a = a + b`, see [this answer](http://stackoverflow.com/questions/15376509/when-is-i-x-different-from-i-i-x-in-python/15376520#15376520) (disclaimer -- I wrote one of the answers). – mgilson Jul 13 '16 at 06:43
7

It can also help to understand this behavior by taking a look at the bytecode with dis.dis.

In[5]: dis('t[0] += [1]')
  1           0 LOAD_NAME                0 (t)
              3 LOAD_CONST               0 (0)
              6 DUP_TOP_TWO
              7 BINARY_SUBSCR
              8 LOAD_CONST               1 (1)
             11 BUILD_LIST               1
             14 INPLACE_ADD
             15 ROT_THREE
             16 STORE_SUBSCR
             17 LOAD_CONST               2 (None)
             20 RETURN_VALUE
  • The value of t[0] is placed on top of the stack with BINARY_SUBSCR, which is a (mutable) list in this case.
  • The value on the top of the stack has += [1] performed on it with INPLACE_ADD, where in this case the top of the stack refers to the list inside the tuple.
  • The assigning of t[0] to the top of the stack occurs with STORE_SUBSCR, which fails here as t itself is an immutable tuple, raising the error after the += assignment has already occurred.
miradulo
  • 28,857
  • 6
  • 80
  • 93
3

The Python developers wrote an official explanation about why it happens here: https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works

The short version is that += actually does two things, one right after the other:

  1. Take the thing on the right and add it to the variable on the left
  2. Put the result into the variable on the left

In this case, step 1 works because you’re allowed to add stuff to lists (they’re mutable), but step 2 fails because you can’t put stuff into tuples after creating them (tuples are immutable).

In a real program, I would suggest you don't do this because t[0].extend(['c']) does the exact same thing.

ffledgling
  • 11,502
  • 8
  • 47
  • 69
Cheyn Shmuel
  • 428
  • 8
  • 15