Don’t Use Mutable Default Values in Python Functions

Perhaps you have heard the advice that you shouldn’t use mutable default values in Python functions, here is why.

Here is a simple function (about as simple as you can get) that takes one parameter my_list with a default value of [1, 2, 3] and returns that same list.

def create_my_list(my_list=[1, 2, 3]):
    return my_list
Python

Now, let’s call the function and assign the return value of it to a variable that is also called my_list.

def create_my_list(my_list=[1, 2, 3]):
    return my_list

my_list = create_my_list()
print(my_list) # [1, 2, 3]
Python

It is pretty clear that my_list is [1, 2, 3] because there was no list passed into create_my_list() and that is the default value of the my_list parameter.

Now, let’s append an additional value to my_list and create another list.

def create_my_list(my_list=[1, 2, 3]):
    return my_list

my_list = create_my_list()
print(my_list) # [1, 2, 3]
my_list.append(4)
print(my_list) # [1, 2, 3, 4]

my_other_list = my_func()
print(my_other_list) # ???
Python

You would think that the value of my_other_list is [1, 2, 3] because you again didn’t provide any parameters to create_my_list() and that is the default value, but you would be mistaken!

The value of my_other_list is [1, 2, 3, 4]… much to the chagrin of you (and every other Python programmer).

Why?!

Well this has to do with how Python represents variables. Essentially a variable in Python is a pointer, which means that it references a specific place in your computer’s memory where the value of the variable is stored.

What is happening here specifically is that when the function create_my_list(my_list=[1, 2, 3]) is evaluated, the default parameter my_list is given a place in memory where the value [1, 2, 3] is stored. Then, every time you call create_my_list() (without a parameter for my_list) it will refer to that same place in memory. Additionally, when you assign it to a variable, like my_list = create_my_list(), it is still just pointing to the same place in memory!

Wait, what?

To make things a little more explicit, let’s use a builtin Python function called id to tell us exactly where in memory a variable is pointing to.

def create_my_list(my_list=[1, 2, 3]):
    return my_list

my_list = create_my_list()
print(my_list, id(my_list)) # [1, 2, 3] (some long number)
my_list.append(4)
print(my_list, id(my_list)) # [1, 2, 3, 4] (some long number)

my_other_list = my_func()
print(my_other_list, id(my_other_list)) # [1, 2, 3, 4] (some long number)
Python

If you run the commands above, the (some long number) part should all be the same each time, because each different variable is pointing to the same place in memory.

How do I fix it?

copy.copy

One (admittedly hacky) way to fix it is to make a copy of my_list and return it. There are two ways that you can do that, the first more explicit way to do it is with the copy.copy function

import copy

def create_my_list(my_list=[1, 2, 3]):
    return copy.copy(my_list)
...
Python

This will give you the desired behavior and give you a copy of my_list every time you call it.

List slice

Another way to copy the array is with a slice, like this.

def create_my_list(my_list=[1, 2, 3]):
    return my_list[:]
...
Python

This will also give you the desired behavior and give you a copy of my_list every time you call it.

(Recommended) Use None

The way that I recommend handling this case is to set the default value of my_list to None and handling the case inside of the function, like this.

def create_my_list(my_list=None):
    if my_list is None:
        my_list = [1, 2, 3]
    return my_list
Python

This is a clean way to give you the desired behavior and give you a copy of my_list every time you call it.

Want to keep up to date? Subscribe to the newsletter!

Leave a Reply

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