I am not sure I quite understand what's happening in the below mini snippet (on Py v3.6.7). It would be great if someone can explain to me as to how can we mutate the list successfully even though there's an error thrown by Python.
I know that we can mutate a list and update it, but what’s with the error? Like I was under the impression that if there's an error, then the x
should remain the same.
x = ([1, 2], )
x[0] += [3,4] # ------ (1)
The Traceback thrown at line (1) is
> TypeError: 'tuple' object doesn't support item assignment..
I understand what the error means but I am unable to get the context of it.
But now if I try to print the value of my variable x
, Python says it's,
print(x) # returns ([1, 2, 3, 4])
As far as I can understand, the exception has happened after Python allowed the mutation of the list to happen and then hopefully it tried re-assigning it back. It blew there I think as Tuples are immutable.
Can someone explain what's happening under the hood?
Edit - 1 Error From ipython console as an image;
Tuples are immutable, you may not change their contents.
Once a tuple is created, you cannot change its values. Tuples are unchangeable, or immutable as it also is called.
Tuples and lists are the same in every way except two: tuples use parentheses instead of square brackets, and the items in tuples cannot be modified (but the items in lists can be modified). We often call lists mutable (meaning they can be changed) and tuples immutable (meaning they cannot be changed).
Mutating methods are ones that change the object after the method has been used. Non-mutating methods do not change the object after the method has been used. The count and index methods are both non-mutating. Count returns the number of occurrences of the argument given but does not change the original string or list.
My gut feeling is that the line x[0] += [3, 4]
first modifies the list itself so [1, 2]
becomes [1, 2, 3, 4]
, then it tries to adjust the content of the tuple which throws a TypeError
, but the tuple always points towards the same list so its content (in terms of pointers) is not modified while the object pointed at is modified.
We can verify it that way:
a_list = [1, 2, 3]
a_tuple = (a_list,)
print(a_tuple)
>>> ([1, 2, 3],)
a_list.append(4)
print(a_tuple)
>>> ([1, 2, 3, 4], )
This does not throw an error and does modify it in place, despite being stored in a "immutable" tuple.
There are a few things happening here.
+=
is not always +
and then =
.
+=
and +
can have different implementations if required.
Take a look at this example.
In [13]: class Foo:
...: def __init__(self, x=0):
...: self.x = x
...: def __add__(self, other):
...: print('+ operator used')
...: return Foo(self.x + other.x)
...: def __iadd__(self, other):
...: print('+= operator used')
...: self.x += other.x
...: return self
...: def __repr__(self):
...: return f'Foo(x={self.x})'
...:
In [14]: f1 = Foo(10)
In [15]: f2 = Foo(20)
In [16]: f3 = f1 + f2
+ operator used
In [17]: f3
Out[17]: Foo(x=30)
In [18]: f1
Out[18]: Foo(x=10)
In [19]: f2
Out[19]: Foo(x=20)
In [20]: f1 += f2
+= operator used
In [21]: f1
Out[21]: Foo(x=30)
Similarly, the list class has separate implementations for +
and +=
.
Using +=
actually does an extend
operation in the background.
In [24]: l = [1, 2, 3, 4]
In [25]: l
Out[25]: [1, 2, 3, 4]
In [26]: id(l)
Out[26]: 140009508733504
In [27]: l += [5, 6, 7]
In [28]: l
Out[28]: [1, 2, 3, 4, 5, 6, 7]
In [29]: id(l)
Out[29]: 140009508733504
Using +
creates a new list.
In [31]: l
Out[31]: [1, 2, 3]
In [32]: id(l)
Out[32]: 140009508718080
In [33]: l = l + [4, 5, 6]
In [34]: l
Out[34]: [1, 2, 3, 4, 5, 6]
In [35]: id(l)
Out[35]: 140009506500096
Let's come to your question now.
In [36]: t = ([1, 2], [3, 4])
In [37]: t[0] += [10, 20]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-37-5d9a81f4e947> in <module>
----> 1 t[0] += [10, 20]
TypeError: 'tuple' object does not support item assignment
In [38]: t
Out[38]: ([1, 2, 10, 20], [3, 4])
The +
operator gets executed first here, which means the list gets updated (extended). This is allowed as the reference to the list (value stored in the tuple) doesn't change, so this is fine.
The =
then tries to update the reference inside the tuple
which isn't allowed since tuples are immutable.
But the actual list was mutated by the +
.
Python fails to update the reference to the list inside the tuple but since it would have been updated to the same reference, we, as users don't see the change.
So, the +
gets executed and the =
fails to execute. +
mutates the already referenced list
inside the tuple
so we see the mutation in the list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With