3

Inspired by reading footnote 4 of this article.

Consider the following scenario:

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

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

Tuples are immutable. So, as expected, trying to add to the list inside the tuple raises an error.

However if we inspect our tuple, the list has been added to!! (I can imagine this leading to very hard to track down bugs)

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

Also, both extending

>>> t[2].extend([7,8])
>>> t
(1, 2, [3, 4, 5, 6, 7, 8])

and appending

>>> t[2].append(9)
>>> t
(1, 2, [3, 4, 5, 6, 7, 8, 9])

work without raising an error.

So, my questions are:

  1. If tuples are immutable, why is it possible to change a list within a tuple?
  2. Why does the first example raise an error and the other two not?
  3. In the first example, why is the list inside the tuple changed even though an error is raised?
BioGeek
  • 21,897
  • 23
  • 83
  • 145
  • 2
    From the [docs](https://docs.python.org/2/reference/datamodel.html#objects-values-and-types): *The value of an immutable container object that contains a reference to a mutable object can change when the latter’s value is changed; however the container is still considered immutable, because the collection of objects it contains cannot be changed. So, immutability is not strictly the same as having an unchangeable value, it is more subtle.* – Moses Koledoye Sep 23 '16 at 10:26
  • See related [documentation page](https://docs.python.org/3.4/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works). – aluriak Sep 23 '16 at 10:27
  • For 1. see: "Why can tuples contain mutable items?" http://stackoverflow.com/questions/9755990/why-can-tuples-contain-mutable-items/ – Chris_Rands Sep 23 '16 at 10:29

1 Answers1

2

If tuples are immutable, why is it possible to change a list within a tuple?

Because "tuples are immutable" only means you cannot modify the tuple. The list that's referred to from the tuple is not part of the tuple, it doesn't "know" that it is in a tuple, and it has no means to resist being modified.

Why does the first example raise an error and the other two not?

Because of how += works. It calls __iadd__ on the list and then (because __iadd__ is not required to return the original object) attempts to assign the resulting modified object back to the tuple. The first thing succeeds, the second thing fails.

That is, for this case where t[2] has an __iadd__ function, t[2] += [5,6] is equivalent to:

t[2] = t[2].__iadd__([5,6])

In the first example, why is the list inside the tuple changed even though an error is raised?

Because this Python operation doesn't offer what in C++ we'd call a "strong exception guarantee". The first part of the operation has already been performed, and cannot be (or at any rate is not) reversed when the second part fails. For the official version see Why does a_tuple[i] += [‘item’] raise an exception when the addition works?

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699