We know that Python tuples are immutable, good. When I attempt to change the reference of a tuple component I get an exception, as expected. What is not expected, is the component gets changed regardless of the exception, whereas I would have thought tuple immutability guarantees that the object won't be mutable.
Is it a bug, feature or a PEP?
In [6]: x=([1],)
In [7]: type(x)
Out[7]: tuple
In [8]: x[0]+=[2,3]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-8-a73186f99454> in <module>()
----> 1 x[0]+=[2,3]
TypeError: 'tuple' object does not support item assignment
In [9]: x
Out[9]: ([1, 2, 3],)
Interesting point.
The reason it behaves like this is that
x[0]+=[2,3]
translates to
x[0] = x[0].__iadd__([2,3])
which means it first calls __iadd__
, which modifies the list in place, and only then attempts to perform the illegal assignment into the tuple.
(Of course, it's easy to workaround (e.g. @luispedro's answer), but I understand your question is not about how to workaround it.)
Is it a bug, feature or a PEP?
Hard to say. I tend to vote for "a bug", because of the Principle of Least Astonishment. One would expect x[0].extend(y)
to behave like a=x[0]; a.extend(y)
to behave like a=x[0]; a+=y
to behave like x[0]+=y
.
A possible fix (at least for python built-in types) can be to required that __setitem__(self, k, v)
should translate to no-op in case self[k] is v
. (and custom classes overriding __setitem__
should obey).