13

I was just toying around in the interpreter and ran across something that I do not understand. When I create a tuple with a list as one the elements and then try to update that list, something strange happens. For example, when I run this:

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

I get:

TypeError: 'tuple' object does not support item assignment

Which is exactly what I expected. However then when I reference the tuple again, I get:

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

So the list was in fact updated even though python threw an exception. How does that work? I can't imagine a scenario where I would actually want to do something like this, but I still would like to understand what is going on. Thank you.

Baltic Sun
  • 185
  • 7

1 Answers1

28

This is actually documented in the Python docs.

EDIT: Here's a summary so that this is a more complete answer.

  1. When we use +=, Python calls the __iadd__ magic method on the item, then uses the return value in the subsequent item assignment.
  2. For lists, __iadd__ is equivalent to calling extend on the list and then returning the list.
  3. Therefore, when we call tup[3] += [6], it is equivalent to:

    result = tup[3].__iadd__([6])
    tup[3] = result
    
  4. From #2, we can determine this is equivalent to:

    result = tup[3].extend([6])
    tup[3] = result
    
  5. The first line succeeds in calling extend on the list, and since the list is mutable, it updates. However, the subsequent assignment fails because tuples are immutable, and throws the error.
Karin
  • 8,404
  • 25
  • 34
  • 2
    I think the answer would benefit from the addition that it is because the extend isn't just `tup[3].__iadd__([6])`; it is `tup[3] = tup[3].__iadd__([6])`. It's merely convenient that `list.__iadd__` returns the same object that it is mutating. – zondo Aug 21 '16 at 22:59
  • Thanks for the link to the docs! That's a big help. – Adam Smith Aug 21 '16 at 23:00
  • Thanks, I didn't catch this in the docs. So to make sure I understand this right, basically in my example the list is updated because i am just doing tup[3].__iadd__([6]) and THEN python attempts (and fails at) the item assignment on the tuple? So I get the exception saying that it failed, but by that point we have already updated the list? – Baltic Sun Aug 21 '16 at 23:21
  • I updated my answer to try to summarize what the docs say - I hope that's ok! :) – Karin Aug 21 '16 at 23:55