Logo Valérian de Thézan de Gaussan

The one bug every Python developer will encounter once.

Valerian Valerian
May 6, 2023
3 min read

I have been explaining it over and over to juniors the past few years, so I decided to write an article about it.

It is simple to understand, but if you do not know about it, you can spend hours to understand what is wrong. Let this article save you time.

Say I have a function that takes an item and an items list as parameters. The function then append the item at the end of the list.

def append_item(item, item_list=[]):
item_list.append(item)
return item_list
 
# First time calling the function
result1 = append_item(1)
print(result1)  # Output: [1]
 
# Second time calling the function
result2 = append_item(2)
print(result2)  # Output: [1, 2], but we expected [2] ?!

This is a simple example, but imagine a much bigger function, where it is more difficult to track back that this list is keeping its state!

So why is this list keeping the values of the previous calls? It is because the default parameter is created only once when the function is defined, and the same object is reused every time the function is called without providing that argument.

In this example, the item_list default parameter is initialized only once, and the same list is reused in all following function calls. When we append an item in the second function call, it gets appended to the same list from the first call, resulting in the list containing the previous value.

To fix this, it is recommended to use None as the default value of the parameter and check for it within the function. If the value is None, assign a new empty list. Here’s an example:

def append_item(item, item_list=None):
    if item_list is None:
    item_list = []
    item_list.append(item)
    return item_list
 
# First function call
result1 = append_item(1)
print(result1)  # Output: [1]
 
# Second function call
result2 = append_item(2)
print(result2)  # Output: [2] (now it is correct)

By using None as the default value and creating a new list inside the function when item_list is not provided, we avoid the issue.

Be careful: it is true for lists, but also dicts, and generally any mutable default parameter.

I reckon that it is less readable now with the if clause. It is one of the few caveats of Python in my opinion.

Anyway, hope it will help!

Link to the official documentation about this