Python Programming – List Mutation

A Surprising Feature of Python Lists

What do you expect the output of the following Python code to be?

def mutate_or_not(a_list):
    a_list[0] = "I've changed"


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

Depending on how well you understand how Python works under the hood, you may be surprised at this result. You might reason that since my_list is defined in the global scope, it has no business being changed when its value is passed into mutate_or_not() as a paramter. Let’s explore what is happening here.

You can step through the code with a great online visualization tool here.

Python List Mutation

Notice how the list is pointed to by both the my_list variable in the global frame AND the the a_list parameter inside mutate_or_not().

Compare the situation above to the following:

def mutate_or_not(an_int):
    an_int = 7


my_int = 5
mutate_or_not(my_int)
print(my_int)

>>> 5

Look at the image below:

Code tracing available here. Can you see how there is no link between my_int (in the global frame), and an_int, the function parameter?

A Practical Example of Python List Mutation

Suppose you wish to rotate the items in a list. After some thinking, you might come up with a solution like this:

def rotate_list(lst, n):
    n = n % len(lst)
    lst = lst[-n:] + lst[:-n]


s1 = [1, 2, 5, 4]
rotate_list(s1, 1)
print(s1)

You might expect the output to be a successfully rotated list: [4, 1, 2, 5]. However, it doesn’t work!

But didn’t you just say that lists were mutable?

The issue here is that when we reassign lst inside the function, the original assignment is lost, and lst now just references the local parameter lst with the new values. The original lst defined outside of the function remains intact.

So what can we do about this? Well one solution is given below, but it is very clumsy and not practical. It does illustrate the issue though so it’s worth looking at:

def rotate_list_2(lst):
    lst[0], lst[1], lst[2], lst[3] = lst[3], lst[0], lst[1], lst[2]


s1 = [1, 2, 5, 4]
rotate_list_2(s1)
print(s1)

>>> [4, 1, 2, 5]

The reason this works is that we do not redefine lst inside the function. Instead we mutate its elements.

However a much better solution is to use Python list slicing, as discussed for example here. In Python, my_list[:] refers to the whole list. Using this fact, we can rewrite our rotate_list() function and leverage the immutability of Python lists to achieve the desired result:

def rotate_list(lst, n):
    n = n % len(lst)
    lst[:] = lst[-n:] + lst[:-n]


s1 = [1, 2, 5, 4]
rotate_list(s1, 1)
print(s1)

Mutable and Immutable Data Types in Python

Mutability in Python doesn’t just apply to lists and integers, but other data types as well. We just focused on lists in this article to keep things programmatic. Below is a table for easy reference.

mutable immutable
list
dictionary
set
user-defined classes
int
float
decimal
bool
string
tuple
range

Conclusion

This article has been about the mutability of lists in Python programming. Knowing how this works will help you to avoid some potentially confusing and time-consuming bugs in your code.

Happy coding!

Sharing is caring!

Leave a Reply

Your email address will not be published. Required fields are marked *