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
PythonNow, 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]
PythonIt 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) # ???
PythonYou 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)
PythonIf 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)
...
PythonThis 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[:]
...
PythonThis 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
PythonThis is a clean way to give you the desired behavior and give you a copy of my_list
every time you call it.
Leave a Reply