Mutability and Immutability in Python — Let’s Break It Down

Have you ever modified a variable without knowing it or wanting to?

In this article, we’ll address that by covering id and type, mutable objects, immutable objects, why it matters, how differently does Python treat mutable and immutable objects, how are arguments passed to functions, and what does that imply for mutable and immutable objects. I include examples in Python3.

What is id?

Id is a built-in function in Python. It gives us the ability to check the unique identifier of an object. Let’s take a look at how this works.

>>> a = 1>>> a1

We see that a is an object that contains the value 1. Now let’s check the id, the unique identifier, of that object.

>>> id(a)10105088

The unique identifier is pointing to a location in memory, which is an object. Let’s try another one.

>>> b = 2>>> b2>>> id(b)10105120

You can see that id(a) is different from id(b) but we can also run a test.

>>> a = 1>>> b = 2>>> id(a) == id(b)False

Let’s assign another variable to 2 and check the id.

>>> a = 1>>> b = 2>>> c = 2>>> c2>>> id(c)10105120

Hmm, id(c) looks the same as id(b). Why is that? Let’s run a test.

>>> id(c) == id(b)True

They have the same unique identifier because c is referencing an object that contains the value 2, and b is also referencing the same object that contains the value 2. Okay, let that sink in for a moment. Wow, right?

They’re both pointing to the same object that contains a value 2.

>>> id(c)10105120>>> id(b)10105120

The object, 10105120, is the unique identifier. It’s the location in memory. Objects can have multiple variables. If you’re debugging a program, this might be a useful function. Let’s now try this with strings.

Note: You can also use “is” to check if two variables have the same object id.

>>> list1 = [1, 2, 3]>>> list2 = list1>>> list1 is list2True

Let’s introduce some more terminology.

The same list above has two different names, list1 and list, and we can say that it is aliased. Variables refer to objects and if we assign one variable to another, both variables refer to the same object. That is what aliasing means.

Let’s also talk about cloning. If we want to modify a list and also keep a copy of the original, we need to make a copy of the list. This process is called cloning. Taking any slice of a list creates a new list.

What are mutable objects?

Python

Mutable objects:list, dict, set

A program stores data in variables that represent the storage locations in the computer’s memory. The contents of the memory locations, at any given point in the program’s execution, is called the program’s state.

Some objects in Python are mutable, and some are immutable. First, we’ll discuss mutable objects. A mutable object is a changeable object and its state can be modified after it is created.

If we want to change the first value in our list and print it out, we can see that the list changed, but the memory address of the list is the same. It changed the value in place. That’s what mutable means.

The id of my_list[0] is 139905997708792 when the value of the first element is ‘sugar glider’. The id of my_list[0] is 139905997708400 after we change the value to ‘rabbit.’ Notice they are two different ids.

When we modify a list and change its values in place, the list keeps the same address. However, the address of the value that you changed will have a different address.

The id of my_list still remained the same at 139929780579208.

What are immutable objects?

Python

Immutable objects:integer, float, string, tuple, bool, frozenset

An immutable object is an object that is not changeable and its state cannot be modified after it is created.

In Python, a string is immutable. You cannot overwrite the values of immutable objects.

We get a TypeError because strings are immutable. We can’t change the string object.

Let’s also talk about tuples.

Immutability on tuples is only partly true. The tuple itself cannot be modified, but objects referenced by the tuple can be modified. If the tuple has an immutable field like a string, then the tuple cannot be modified and it is sometimes called “non-transitive immutability.” But a mutable field like a list can be edited, even if it’s embedded in the “immutable” tuple.

Why do mutable and immutable objects matter and how differently does Python treat them?

Numbers, strings, and tuples are immutable. Lists, dictionaries, and sets are mutable, as are most new objects you’ll code with classes.

Immutability may be used to ensure that an object remains constant throughout your program. The values of mutable objects can be changed at any time and place, whether you expect it or not.

You can change a single value of a mutable data type and it won’t change its memory address. However, you can’t change a single value of an immutable type. It will throw an error.

How are arguments passed to functions and what does that imply for mutable and immutable objects?

The way that the Python compiler handles function arguments has to do with whether the objects in the function arguments are mutable or not immutable.

If a mutable object is called by reference in a function, the original variable may be changed. If you want to avoid changing the original variable, you need to copy it to another variable.

When immutable objects are called by reference in a function, its value cannot be changed.

Let’s look at this Python script and guess what it will print:

def increment(n): n += 1

b = 9increment(b)print(b)

Think about it and then continue reading for the answer.

The variable b refers to the object with value 9. When we pass b as a function argument to increment(n) function, the local variable n refers to the same object. However, integers are immutable so we need to create a new object with the value 10 and assign it to the variable n. The variable n is pointing to a different object from what b is pointing. Now, n refers to an object with value 10, but b still refers to an object with value 9. When we print(b), we get the answer 9.

The answer: 9

Let’s look at another Python script and guess what it will print:

def increment(n): n.append(4)

my_list = [1, 2, 3]increment(my_list)print(my_list)

Think about it, perhaps draw a visualization, and then continue reading for the answer.

The variable my_list refers to a list object that contains references to three integers. Lists are mutable but integers are immutable. When we pass my_list as a function argument to increment(n) function, the function has the local variable n refer to the same object that my_list refers.

Since lists are mutable, the .append() method is able to modify the list in place. No new object is created and when we print my_list, we get the answer [1, 2, 3, 4].

The answer: [1, 2, 3, 4]

Let’s look at another Python script to understand more about function parameters and why mutability and immutability matter.

We pass both lists as function parameters to the assign_value(n, v) function. The function has the local variable n refer to the same object that list1 refers, and the local variable v refers to the same object that list2 refers.

The function body reassigns n to what v is referring. Now n and v are referring to the same object.

The variables n, v, and list2 all point to the list object [4, 5, 6], while list1 still points to the list object [1 2, 3]. This is why when we print list1, we get the answer: [1, 2, 3]

The answer: [1, 2, 3]

How do we write a function that returns a copy of a list? Here’s one way of doing it. Let’s look at copy_list(l) function.

def copy_list(l): return l[:]

my_list = [1, 2, 3]new_list = copy_list(my_list)

We pass my_list as a function parameter to the copy_list(l) function. The function has the local variable l refer to the same object that my_list refers. When we use the slice operation [:], it creates a copy of a list and when we return that copy, we are returning the reference to that copy. Now, new_list refers to a different object than what my_list refers.