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 = "I've changed" my_list = [1, 2, 3] mutate_or_not(my_list) print(my_list)
["I've changed", 2, 3]
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.
Notice how the list is pointed to by both the
my_listvariable in the global frame AND the the
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, lst, lst, lst = lst, lst, lst, lst 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.
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.