Python

Why you Shouldn’t use lists as default function arguments in Python

DNA image symbolically shows the mutation. Lists are mutable objects. This is why DNA image is used for this article.

Lists are containers. you can put anything in them, objects and strings, even lists. So, if you want to get a list of items and do something with them in your function, using an empty list as one of the default arguments must be your best choice. right? Well… No. Let’s see why. Look at this function

def say_all_names(first_name, other_names=[]):
    other_names.append(first_name)
    print(other_names)

This function gets a name and a list of names. appends the name to the list of names and prints the resulting list. let’s check if it works.

say_all_names('John') #prints ['John']


So far so good. So, what’s the problem? why am I taking your time here? Just let me show you another thing. If it doesn’t get exciting, hit the close button and never ever open this website again.

OK! Now it’s time to see the surprise (drum rolls). Let’s run our function once again:

say_all_names('Sarah')

What do you think the output is? I bet you expect only Sarah right? Let me surprise you. It is not. The output is:

['John', 'Sarah']

But Why?

The explanation is very easy as I expand on it. It has two sides.

1- Functions are objects in python.

2- Lists are mutable i.e., their items can change but the container stays the same. They are called mutable because they can change (mutate) without changing who they are. Like a virus.

We know everything is an object in python. Functions are also objects. They have attributes like other types of objects. the __defaults__ attribute of the function holds the default values for arguments. Let’s look at it:

def say_all_names(first_name, other_names=[]):
    other_names.append(first_name)
    print(other_names)
print(say_all_names.__defaults__) #this prints -> ([],)

Now we run the function with input and then look at the default value of the arguments:

say_all_names('John') #this prints -> ['John']
print(say_all_names.__defaults__) #this prints -> (['John'],)

Do you see what just happened? When we called the function once, ‘John’ was appended to the other_names list and since it is in the __defaults__ attribute of the function, it also changes. The next time the function is called, the default values of the arguments are read from the __defaults__ attribute and are loaded into the function. All this happens because lists are mutable. You can add or delete items in the list without changing their identity. Dictionaries are also mutable. Ints, strings, and tuples on the other hand are immutable. If you want to know more about mutable and immutable types and their difference, read my article on mutability in python here.

What if you absolutely need this functionality?

If you need to define a list as one of the arguments of the function. you should put it as None in the function definition. The refactored version of our function can be defined like this:

def say_all_names(first_name, other_names=None):
    if other_names is None:
        other_names = []
    other_names.append(first_name)
    print(other_names)

You can access the source code of this article on my Github here. Please let me know what you think and don’t forget to give me a star on Github if you have found this article helpful.

Leave a Reply

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