3

I'm trying to modify a list in a tuple, the append method works, while += operator works yet with an exception raised saying tuple could not be modified. I know a tuple is immutable, but I'm not trying to mutate it. Why this happen?

In [36]: t=([1,2],)

In [37]: t[0].append(123)

In [38]: t
Out[38]: ([1, 2, 123],)

In [39]: t[0]+=[4,5,]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-39-b5b3001fbe03> in <module>()
----> 1 t[0]+=[4,5,]

TypeError: 'tuple' object does not support item assignment

In [40]: t
Out[40]: ([1, 2, 123, 4, 5],)
zhangxaochen
  • 32,744
  • 15
  • 77
  • 108
  • Also see the [official explanation in the Python FAQ](http://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works). – user2357112 Jan 26 '14 at 08:38
  • @user2357112, thank you~ and thank all the answers below! I was thinking there must be an answer for this, just didn't know how to describe and Google this ;) – zhangxaochen Jan 26 '14 at 11:30

4 Answers4

7

+= is the in-place addition operator. It does two things:

  • it calls obj.__iadd__(rhs) to give the object the opportunity to mutate the object in-place.
  • it rebinds the reference to whatever the obj.__iadd__(rhs) call returns.

By using += on a list stored in a tuple, the first step succeeds; the t[0] list is altered in-place, but the second step, rebinding t[0] to the return value of t[0].__iadd__ fails because a tuple is immutable.

The latter step is needed to support the same operator on both mutable and immutable objects:

>>> reference = somestr = 'Hello'
>>> somestr += ' world!'
>>> somestr
'Hello world!'
>>> reference
'Hello'
>>> reference is somestr
False

Here a immutable string was added to, and somestr was rebound to a new object, because strings are immutable.

>>> reference = somelst = ['foo']
>>> somelst += ['bar']
>>> somelst
['foo', 'bar']
>>> reference
['foo', 'bar']
>>> reference is somestr
True

Here the list was altered in-place and somestr was rebound to the same object, because list.__iadd__() can alter the list object in-place.

From the augmented arithmetic special method hooks documentation:

These methods are called to implement the augmented arithmetic assignments (+=, -=, *=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=). These methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self).

The work-around here is to call t[0].extend() instead:

>>> t = ([1,2],)
>>> t[0].extend([3, 4, 5])
>>> t[0]
[1, 2, 3, 4, 5]
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
4

Because t[0] += [4,5,] is interpreted as:

t[0] = t[0].__iadd__([4,5,])

t[0]__iadd__([4,5]) succeed, while t[0] = .. fail.


list.__iadd__ extend the list, and return itself.

>>> lst = [0]
>>> lst2 = lst.__iadd__([1])
>>> lst
[0, 1]
>>> lst2
[0, 1]
>>> lst is lst2
True
falsetru
  • 357,413
  • 63
  • 732
  • 636
1

In fact you do change the tuple:

The + operator for lists creates a new list and you try to mutate your tuple by replacing the old list by the new one. appendmodifies the list in the tuple, therefore it works.

OBu
  • 4,977
  • 3
  • 29
  • 45
  • I don't think I'm **replacing** the old list with a new one, since `l=[1,]; print id(l); l+=[3]; print id(l)` prints the same id. – zhangxaochen Jan 26 '14 at 08:34
  • @zhangxaochen: In-place addition on a mutable object does two things: Mutate the object, and return `self`, so that the reference can be re-bound. – Martijn Pieters Jan 26 '14 at 08:34
1

When we say tuple is immutable, it means that the elements of a tuple (which are references to other objects), cannot be changed (read as, cannot be made to refer other objects).

So, when you say,

t[0].append(123)

You are not changing the element at index 0, to refer some other object. Instead, you are making changes to the same object, which is perfectly okay as per the tuple.

When you say,

t[0] += [4,5,]

Python internally calls __iadd__ (stands for inplace add) method, which can be understood like this

t[0] = t[0] + [4,5,]

which means that, we take the object t[0], adding it with [4,5,] to get a new object and that new object is being assigned back to t[0]. Now we are trying to mutate the tuple (making an element of a tuple refer to some other object).

That is why you see

TypeError: 'tuple' object does not support item assignment

in the later case.

thefourtheye
  • 233,700
  • 52
  • 457
  • 497