very-cool-group

mutable-default-args

Default arguments in Python are evaluated once when the function is defined, not each time the function is called. This means that if you have a mutable default argument and mutate it, you will have mutated that object for all future calls to the function as well.

For example, the following append_one function appends 1 to a list and returns it. foo is set to an empty list by default.

>>> def append_one(foo=[]):
...     foo.append(1)
...     return foo
...
See what happens when we call it a few times:
>>> append_one()
[1]
>>> append_one()
[1, 1]
>>> append_one()
[1, 1, 1]
Each call appends an additional 1 to our list foo. It does not receive a new empty list on each call, it is the same list everytime.

To avoid this problem, you have to create a new object every time the function is called:

>>> def append_one(foo=None):
...     if foo is None:
...         foo = []
...     foo.append(1)
...     return foo
...
>>> append_one()
[1]
>>> append_one()
[1]

Note:

  • This behavior can be used intentionally to maintain state between calls of a function (eg. when writing a caching function).
  • This behavior is not unique to mutable objects, all default arguments are evaulated only once when the function is defined.
or-gotcha

When checking if something is equal to one thing or another, you might think that this is possible:

# Incorrect...
if favorite_fruit == 'grapefruit' or 'lemon':
    print("That's a weird favorite fruit to have.")
While this makes sense in English, it may not behave the way you would expect. In Python, you should have complete instructions on both sides of the logical operator.

So, if you want to check if something is equal to one thing or another, there are two common ways:

# Like this...
if favorite_fruit == 'grapefruit' or favorite_fruit == 'lemon':
    print("That's a weird favorite fruit to have.")

# ...or like this.
if favorite_fruit in ('grapefruit', 'lemon'):
    print("That's a weird favorite fruit to have.")