Mutate tuple of lists getting "'tuple' object does not support item assignment“ [duplicate]

StackOverflow https://stackoverflow.com/questions/21361281

Pregunta

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],)
¿Fue útil?

Solución

+= 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]

Otros consejos

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

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.

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top