9

I was reading this interesting post https://asmeurer.github.io/blog/posts/tuples/

At the footnote author present this example

>>> t=1,2,[3,4]
>>> t
(1, 2, [3, 4])
>>> t[2]+=[5,6]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Although Python has raised a exception but it did change the tuple

>>> t
(1, 2, [3, 4, 5, 6])

Not sure what is going on here, is this a bug?

The behavior is same in 2.7.10 and 3.5.1

DevC
  • 7,055
  • 9
  • 39
  • 58

3 Answers3

3

So the behavior of += is a bit weird. For immutable objects like integers it has to assign a new object to the same name:

a = 4
a += 3

For mutable types, e.g. lists, the object is altered in place, but also returns the same object to be assigned to the same name. The first step works with your tuple, but not the second.

That's why after the list is extended the exception is raised.

Daniel
  • 42,087
  • 4
  • 55
  • 81
3

This is because the += operator (which is __iadd__, or in-place add internally) actually do return something after the assignment happened. In a list this translates into an extend call (or something like it) and thus the new items already got in, before the reference to the list was returned for assignment to t[2], which then raise the exception. Now you check the value you can see that it got added. The following is the minimum code to demonstrate this:

>>> class AddIDemo(object):
...     def __init__(self, items):
...         self.items = list(items) 
...     def __iadd__(self, other):
...         print('extending other %r' % other) 
...         self.items.extend(other.items)
...         print('returning self to complete +=')
...         return self 
...     def __repr__(self):
...         return self.items.__repr__() 
... 
>>> demo = AddIDemo([1, 2])
>>> demo += AddIDemo([3, 4]) 
extending other [3, 4]
returning self to complete +=
>>> demo 
[1, 2, 3, 4]
>>> t = 1, 2, demo 
>>> t 
(1, 2, [1, 2, 3, 4])
>>> 
>>> t[2] += AddIDemo([5, 6]) 
extending other [5, 6]
returning self to complete +=
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t 
(1, 2, [1, 2, 3, 4, 5, 6])
>>> 

Note that I added some more print statements to show that the function being called and how the operation happened as it would do in a standard list manipulation via += or __iadd__.

metatoaster
  • 17,419
  • 5
  • 55
  • 66
0

This is not a bug but the state you will be left in when the exception is raised might be confusing.

A tuple by design must not be changed but that only applies to the tuple and not to it's items. If it contains a mutable item that item can be modified. In your case the list is such an item that can be modified.

Now the += operator maps more or less to do a + and then assign it to the original variable. The + operation is possible, you can add two lists. For opimization reason the + is implemented like an extend() and updates in place. But the assignment fails since the tuple item can not be chanced.

There are basically two lessons:

  1. Python operators can be implemented by type and might behave different then the trivial expectations.
  2. If an exception is raised while calling a function and not handled properly it might have done something before which is not cleaned up.
Klaus D.
  • 13,874
  • 5
  • 41
  • 48