I find this very interesting. These examples are from fluent python.
In [145]: t = (0, 1, [2, 3])
In [146]: t[2] += [4, 5]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[146], line 1
----> 1 t[2] += [4, 5]
TypeError: 'tuple' object does not support item assignment
In [147]: t
Out[147]: (0, 1, [2, 3, 4, 5])
Likewise…
In [117]: def foo(a, b):
...: a += b
...:
In [118]: x = [1, 2]
In [119]: y = [3, 4]
In [120]: foo(x, y)
In [121]: x
Out[121]: [1, 2, 3, 4]
In [122]: z = (1, 2)
In [123]: w = (3, 4)
In [124]: foo(z, w)
In [125]: z
Out[125]: (1, 2)
The reason of these behaviors are easier to understand if we take a look at the methods of tuple.
In [149]: dir((0, 1))
Out[149]:
['__add__',
'__class__',
'__class_getitem__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__getstate__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'count',
'index']
Whenever += is called for tuple, say, a += b, what’s actually happening is a + b and then a = a + b. So, += doesn’t directly modify a. Instead, it assigns a + b to a.
Similarly t[2] += [4, 5] performs [2, 3] += [4, 5] first (which is ok), and then assign it result to t[2] (which is an error). That’s why we get [2, 3, 4, 5].