Python: Mutable, Immutable… everything is object!

Josue Cerron
7 min readJun 27, 2023

--

When you are learning Object Oriented Programming (OOP) in Python for the first time, you will see that an instantiation of a class is also called an object (you can visit this site to learn more about OOP). But let me tell you that it is not the only thing that Python calls an object because everything is an object!

Everything is an object!

Here, in this blog, I am going to cover the concepts of an object in Python and related topics. I will try to give you many examples and do analogies to make the concepts easy to understand, so sit back, relax and enjoy the show.

Understanding id and type built-in functions

Before we dive in with id and type, let’s answer the question: What is an object? According to the documentation of Python:

Objects are Python’s abstraction for data. All data in a Python program is represented by objects or by relations between objects. Every object has an identity, a type and a value.

As we read above, every object has a unique id. This id is an integer and it is guaranteed to be unique during its lifetime. We can think of id as the address memory of the object. To see this identity we use the id() function as in the image below:

Python 3 interpreter

In the image above, I declare a variable named name an assigned it a value "Josue". Then I used the id() function to get the identity of the object "Josue". Think of variables as aliases and they are just pointing to the object. In other words, we are creating a reference to that object like below:

An alias called name pointing to the Object “Josue”

Now, let’s understand the type() function. The type of an object allows us to determine what kind of operations it can do (for instance, “does it have a length?”). We use the type() function to see it:

Python 3 interpreter using bpython

Above, I declared two variables: age and fruit, then I used type() to get the of each object. In the case of age, its type is int (integer) and for fruit its type is str (string). When we passed just an argument to type(), it gives us the type of the object but there is another use of type when passing it three arguments (check this article out to learn more).

Python tells us every object has a type, id and a value. A value of an object can change and it depends on what is the type of that object.

Mutable and Immutable objects

When an object can change its value without changing its identity, it is called mutable and if a object cannot do it, it is called immutable. Below, there is a list of mutable and immutable objects:

Image from this site

There is a distinction in memory between mutable and immutable objects when they are created. When an immutable object is created, it is allocated in a specific memory location (like every object), but if another immutable one is created with the same type and value, Python will just make to alias refer to the same memory location of the first object instead of creating a new one. This is known in Python as “interning” or “catching” of immutable objects. This action is implemented for optimizations purposes.

Pre-allocation for int objects

Another point to note is the case of integers. With object catching technique, when the Python interpreter starts, int objects are pre-allocated in the range from -5 to 257, a total of 262 integers. This is done to increase the efficiency because it allows to reuse int objects instead of creating new ones. To do this pre-allocation Python uses two macros called: NSMALLPOSINTS (257 not inclusive) and NSMALLNEGINTS(-5 inclusive).

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* References to small integers are saved in this array so that they
can be shared.
The integers that are saved are those in the range
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
#endif
#ifdef COUNT_ALLOCS
Py_ssize_t quick_int_allocs;
Py_ssize_t quick_neg_int_allocs;
#endif

If you want to know in more detail about this topic, check this site out and also this.

Well, that was dense; let me show you some examples to clarify what I said:

Memory location for immutable objects with the same value

Above, I declared two aliases referencing, at first sight, to the same object. To verify our hypothesis, I used the id() function to get the identity. Voila, both name_1 and name_2 aliases refer to the same object. Much better? If it is not the case, please check this article out.

That is not the case for mutable objects, because If we create a new alias referencing an object with the same type and values as the first one, they will refer to different memory locations:

Memory location for mutable objects with the “same” lists

As you can see, apparently, both list_1 and list_2 refer to the same object but if we use the id() function, we can see that they refer to different objects. However, there is one way to make list_1 and list_2 to refer to the same object. The syntax is list_1 = list_2 = [1, 2, 3, 4] . Cool, isn’t it?😜

Let’s check some examples about how immutable and mutable works. First, examples of immutable objects.

Trying to change the value of name variable at index 1

In the given example, I am trying to change the variable name at index 1 by replacing letter o, but it gives me an error. The same behavior with tuples:

Trying to change the tuple at index 0

Now, it is time to check examples for mutable objects.

List and set

Exceptions in immutability

What I mean by writing exceptions in immutability is that the immutable objects such as frozen set, can contain reference to a mutable object. For instance, a tuple can contain a list or a set. The image below illustrate what I am saying:

Modifying a list inside a tuple

As we can notice, the list inside the tuple has been modified. That’s because a list is a mutable object and I created an alias to it to modify it.

The concepts of mutable and immutable objects is important to know and also understand how they work because I am going to explain how they are treated when used in functions.

How arguments are passed to functions

When we are working with mutable and immutable objects the behavior on functions are different because if we pass a mutable object by reference, it is going to be modified (To avoid this, we can use a copy of the list). Let me show you an example:

Define a list an call by reference in a function

In the previous image, list_ was passed by reference to the function change_list() , then, I used in-place concatenation to add a new item to the list. That is why the original list was changed. Maybe a graphic can explain a little bit more:

Illustration for passing a mutable object to a function

Up here, what I am showing you is how the parameter of the function called list_1 is referencing the same object as list_. For that reason, when I update list_1, list_ is also updated.

In the case of immutable objects is a little different. The variables does not change their values when passing to a function. That is because we passing just the values of the objects. It is call passing by value. Let’s check the example below:

Passing immutable object to function

We can see above that the value of num_ is the same after calling the function change_integer().

Well, I hope you enjoyed the show. Below, I am going to share with you some blogs that help me to write this blog: K.Wong and megha mohan:

See you next time!

--

--