Blog

Python Mutable Default Arguments

A default argument in Python is evaluated once, when the function is defined. If that default is a list or a dict, every call shares the same object.

This is the single most reported surprise in Python, and it produces bugs that look like the function is remembering old calls.

The bug

Three calls, each meant to start with an empty basket.

# the bug
def add_item(item, basket=[]):
    basket.append(item)
    return basket

print(add_item('apple'))
print(add_item('bread'))
print(add_item('milk'))

It prints:

['apple']
['apple', 'bread']
['apple', 'bread', 'milk']

The basket keeps growing. Nobody passed it in, and it was never emptied.

Why it happens

The default is stored on the function object itself. You can look at it directly.

# why it happens
def add_item2(item, basket=[]):
    basket.append(item)
    return basket

print(add_item2.__defaults__)
add_item2('x')
print(add_item2.__defaults__)

It prints:

([],)
(['x'],)

__defaults__ holds the same list the function hands out. Appending inside the body appends to that stored list, so the next call sees it.

The fix

Use None as the default and build the real value inside the function.

# the fix
def add_item3(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

print(add_item3('apple'))
print(add_item3('bread'))

It prints:

['apple']
['bread']

Now the list is created per call, because the body runs per call.

It applies to every mutable default

Lists, dicts and sets all behave this way. Immutable defaults such as numbers, strings, tuples and dates are safe, because nothing can change them.

# it applies to every mutable default
import datetime

def log(message, seen={}, stamps=[], when=datetime.date(2020, 1, 1)):
    seen[message] = True
    stamps.append(when)
    return len(seen), len(stamps)

print(log('first'))
print(log('second'))

It prints:

(1, 1)
(2, 2)

The date default is fine. The dict and the list are not.

The same trap in a class body

A list assigned in the class body belongs to the class, so every instance shares it.

# the same trap in a class body
class Basket:
    items = []          # shared by every instance

a, b = Basket(), Basket()
a.items.append('apple')
print(b.items, a.items is b.items)

class BasketFixed:
    def __init__(self):
        self.items = []

c, d = BasketFixed(), BasketFixed()
c.items.append('apple')
print(d.items, c.items is d.items)

It prints:

['apple'] True
[] False

Assign mutable attributes in __init__, not in the class body.

What to remember

  • Default arguments are evaluated once, at definition time.
  • A mutable default is shared by every call that does not pass one.
  • Use None as the default and create the value inside the function.
  • The same rule applies to mutable attributes in a class body.

Dataclasses raise a ValueError if you try this, which is one of the few places Python stops you rather than letting it happen quietly.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Leave a Reply