* Python: Mutable and Immutable Objects
*
* Date: 24.08.2023
*\
The concept of mutable and immutable objects seems to take many Python practitioners by surprise the moment they first encounter it. Many times this first encounter involves a list and revolves around some list element, that seems to have changed out of nowhere. But it hasn't, let's look at the details.
id is not a function, that you will use very often in production code, probably even never at all. It is, however, very useful for demonstration purposes, as it returns an integer, that is guaranteed to be unique and constant for a given object. This identification for a certain object is (in the case of the most widely-used Python interpreter CPython) the memory address it is stored at:
>>> b = [1,2,3]
>>> id(a)
1971131930368
>>> id(b)
1971131960512
Thus, while the two lists "a" and "b" are equivalent in the sense that they contain equivalent elements, they are not the same object, because they are not stored at the same address in memory. You can see this situation visualized in Figure 1, which shows the variables "a" and "b" referencing the memory locations 1971131930368 and 1971131960512, where we can find the previously created lists. As you will see, the representation of variables as references will come in handy later.

You can contrast that with
>>> d = 1
>>> id(e)
1971126927600
>>> id(f)
1971126927600
which are equivalent integers and indeed also one and the same object as can be seen in Figure 2, where the variables "c" and "d" obviously point to one and the same memory location 1971126927600 and the object found there.

False
for an easy check on whether two objects are the same.
Mutable objects can change after they have been created, while immutable objects cannot. Suppose you want to change the third element of the list "a", that we defined before, noting that objects of the type "list" are mutable. All you have to do is to assign the changed value to the index you choose
>>> a
[1, 2, 0]
which is an operation, that is impossible, when the underlying object is of an immutable type, e.g. a tuple:
>>> e[2] = 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
As an immutable object cannot be changed, it must be recreated and assigned to the same variable to have the value of the variable altered. Consequently, the only way to change "e" in the way we intended is to generate a new tuple that contains the corresponding values and assigning it to our variable
If you want to know, whether a given object is mutable, you have to look at its type. While a user-defined object will always be mutable, objects of certain built-in types can be mutable or immutable. The following table provides an overview of selected built-in types according to the mutability of their associated objects, sorting them into similar type pairs (which does not necessarily mean, however, that one is a frozen version of the other) where possible.
Immutable | Mutable |
---|---|
bool | |
int, float | |
str | |
bytes | bytearray |
tuple | list |
frozenset | set |
dict |
It has to be noted that after having changed the element 2 of the list "a" from "2" to "0" in Listing 1, the variable "b" (which had been defined separately from "a", but contained an equivalent list) is still unchanged:
[1, 2, 3]
Essentially, the assignment operation we have executed transformed the situation in Figure 1 into the one depicted in Figure 3.

Now let us start from scratch and define "a" and "b" in a slightly different manner, namely by
>>> b = a
Using the function id again, we can see that in contrast to the originally defined "a" and "b" the more recent definition leads to
1321587267328
>>> id(b)
1321587267328
which means that behind the scenes the variables "a" and "b" point to one and the same (list) object. Thus, we have created the situation visualized in Figure 4, which is very similar to the one from Figure 2.

Now let us assign a new value to the third element of the list "a", just like we did before.
Note that we have accessed the object on memory location 1321587267328 through "a" and changed the second element, so now we have the situation depicted in Figure 5.

[1, 2, 0]
>>> b
[1, 2, 0]
This somewhat surprising behavior seems odd at first and bears a certain potential for trouble, especially in larger Python programs. Observing Figures 1 to 5 you can understand, that this is a result of the notion of a Python variable itself and constitutes a deeply engrained memory saving mechanism.
Variables work the same way for mutable and immutable objects and both of them exhibit the behavior described above, only that it is much less obvious in the immutable case, as immutable objects themselves never change. You can see this by defining "c" and "d" by assigning them an immutable object in the same way we did with the list-containing variables "a" and "b" and then asking for their ids:
>>> d = c
>>> id(c)
1321587436752
>>> id(d)
1321587436752
Indeed, "c" and "d" also contain the very same object. Variables in Python obviously act very much like references and not like labels for memory locations, that contain a given value (which is what beginners often expect). Passing around such references is, in a way, like passing pointers or references in the C/C++ world and brings certain risks with it. As an example for the dangers associated with this behavior, let's define a function
... print(id(arg))
and call it with the most recent "a" defined above. The interpreter then passes a reference to the original object as well
1321587267328
and the mutable object is then fully modifiable in a possibly completely different context! The same holds true when a mutable object is stored in the attribute of another object and then left for modification by its methods. In larger applications, this behavior can lead to obscure bugs, that have some characteristics of dangling pointers; it is thus advisable to exercise caution and (when this is not too resource intensive) copy the respective object into a variable that is then a lot safer to modify. More on this in the *next article.