We have all heard about mutable and immutable objects when learning about Python, but what does that actually mean? Let’s open a notebook an experiment a bit.
Let us first see some operations with a mutable object, especifically, we are going to use a list.
lst = [1, 2, 'x', ['l', 'i', 's', 't']]
print(lst)
[1, 2, 'x', ['l', 'i', 's', 't']]
We have just created a list containing two integers, a string and another list with four strings. We know that lists are mutable, so we can change the elements in them using assignments.
lst[2] = 'changed 2'
lst[1] += 3
lst[3][1] = 'a'
print(lst)
[1, 5, 'changed 2', ['l', 'a', 's', 't']]
As expected, we have changed the newly assigned elements. Let us try the same thing with an immutable object, a tuple.
tup = (1, 2, 'x', ['l', 'i', 's', 't'])
print(tup)
try:
tup[2] = 'changed 2'
except TypeError as ex:
print(f'Accessing item 2, TypeError: {ex}')
try:
tup[1] += 3
except TypeError as ex:
print(f'Accessing item 1, TypeError: {ex}')
print(tup)
(1, 2, 'x', ['l', 'i', 's', 't'])
Accessing item 2, TypeError: 'tuple' object does not support item assignment
Accessing item 1, TypeError: 'tuple' object does not support item assignment
(1, 2, 'x', ['l', 'i', 's', 't'])
As we can see, trying to reassign an element of tuple provides a TypeError
, indicating that this object does not actually support that feature.
Therefore, if we do the following
try:
tup[3][1] = 'a'
except TypeError as ex:
print(f'Accessing item 1, TypeError: {ex}')
print(tup)
we should obtain the error as well right…? NO! We will obtain the following
(1, 2, 'x', ['l', 'a', 's', 't'])
Meaning that our immutable tuple has actually changed! In fact, we could even do the following
for _ in range(4):
tup[3].pop()
print(tup)
(1, 2, 'x', [])
Which has completely destroyed the last element of the original tuple. So, what is going on? Didn’t we say that tuples cannot change?
The more attentive may have already realized that I am actually playing a sleight of hands on them. We have mentioned that tuples are immutable, meaning, that their elements cannot change, and that remains true (even though it seems like our tuple is changing).
The trick is on how we are trying to change the element of the tuple. When we use tup[3][1] = 'something'
we are not actually trying to change the 4th element of the tuple, we are accessing the 2nd element of the list stored in the tuple. And as we determined before, lists are mutable and we can perform this operation.
When a list is modified the id of the list does not change. Therefore, the element that is stored in the tuple does not change when we modify it, so the tuple remains unchanged.
Similarly, in the for
loop with tup[3].pop()
, we are accessing and calling the method of the list stored in that typle, which modifies the list. But again, the list id does not vary in the process, therefore, the tuple remains unchanged.
Te get a better grip on what ids are and how they relate to mutable and immutable objects check out this other post.
Here you can see the complete code.
lst = [1, 2, 'x', ['l', 'i', 's', 't']]
print(lst)
lst[2] = 'changed 2'
lst[1] += 3
lst[3][1] = 'a'
print(lst)
tup = (1, 2, 'x', ['l', 'i', 's', 't'])
print(tup)
try:
tup[2] = 'changed 2'
except TypeError as ex:
print(f'Accessing item 2, TypeError: {ex}')
try:
tup[1] += 3
except TypeError as ex:
print(f'Accessing item 1, TypeError: {ex}')
print(tup)
try:
tup[3][1] = 'a'
except TypeError as ex:
print(f'Accessing item 1, TypeError: {ex}')
print(tup)
for _ in range(4):
tup[3].pop()
print(tup)