7.3. Don’t Use Mutable Values for Default Arguments

Python allows you to set default arguments for parameters in the functions you define. If a user doesn’t explicitly set a parameter, the function will execute using the default argument. This is useful when most calls to the function use the same argument, because default arguments make the parameter optional. For example, passing None for the split() method makes it split on whitespace characters, but None is also the default argument: calling ‘cat dog’.split() does the same thing as calling ‘cat dog’.split(None) . The function uses the default argument for the parameter’s argument unless the caller passes one in.

But you should never set a mutable object, such as a list or dictionary, as a default argument. To see how this causes bugs, look at the following example, which defines an addIngredient() function that adds an ingredient string to a list that represents a sandwich. Because the first and last items of this list are often ‘bread’ , the mutable list [‘bread’, ‘bread’] is used as a default argument:

>>> def addIngredient(ingredient, sandwich=['bread', 'bread']):
>>>
>>>     sandwich.insert(1, ingredient)
>>>
>>>     return sandwich
>>>
>>> mySandwich = addIngredient('avocado')
>>> mySandwich
['bread', 'avocado', 'bread']

But using a mutable object, such as a list like [‘bread’, ‘bread’] , for the default argument has a subtle problem: the list is created when the func- tion’s def statement executes, not each time the function is called. This means that only one [‘bread’, ‘bread’] list object gets created, because we only define the addIngredient() function once. But each function call to addIngredient() will be reusing this list. This leads to unexpected behavior, like the following:

>>> mySandwich = addIngredient('avocado')
>>> mySandwich
['bread', 'avocado', 'avocado', 'bread']
>>> anotherSandwich = addIngredient('lettuce')
>>> anotherSandwich
['bread', 'lettuce', 'avocado', 'avocado', 'bread']

Because addIngredient(‘lettuce’) ends up using the same default argu- ment list as the previous calls, which already had ‘avocado’ added to it, instead of [‘bread’, ‘lettuce’, ‘bread’] the function returns [‘bread’, ‘­lettuce’, ‘avocado’, ‘bread’] . The ‘avocado’ string appears again because the list for the sandwich parameter is the same as the last function call. Only one [‘bread’, ‘bread’] list was created, because the function’s def statement only executes once, not each time the function is called. A visualization of the execution of this code is at https://autbor.com/sandwich.

If you need to use a list or dictionary as a default argument, the pythonic solution is to set the default argument to None . Then have code that checks for this and supplies a new list or dictionary whenever the func- tion is called. This ensures that the function creates a new mutable object each time the function is called instead of just once when the function is defined, such as in the following example:

>>> def addIngredient(ingredient, sandwich=None):
>>>
>>>     if sandwich is None:
>>>
>>>         sandwich = ['bread', 'bread']
>>>
>>>     sandwich.insert(1, ingredient)
>>>
>>>     return sandwich
>>>
>>> firstSandwich = addIngredient('cranberries')
>>> firstSandwich
['bread', 'cranberries', 'bread']
>>> secondSandwich = addIngredient('lettuce')
>>> secondSandwich
['bread', 'lettuce', 'bread']
>>> id(firstSandwich) == id(secondSandwich)
False

Notice that firstSandwich and secondSandwich don’t share the same list reference 1 because sandwich = [‘bread’, ‘bread’] creates a new list object each time addIngredient() is called, not just once when addIngredient() is defined.

Mutable data types include lists, dictionaries, sets, and objects made from the class statement. Don’t put objects of these types as default argu- ments in a def statement.