codejump

AcademyPython

Mutable Default Arguments

Why a default value written as a list or dict is created once, shared across every call, and the None-then-create idiom that every codebase uses to avoid it.

3 min readUpdated

3 exercises

Waiting at the end of this page.

The bug in five lines

python
def add_item(item, basket=[]):
    basket.append(item)
    return basket

add_item('apple')   # ['apple']
add_item('banana')  # ['apple', 'banana'] — not ['banana']

The second call was supposed to start from an empty basket, since no basket argument was passed either time. Instead it kept appending to the same list. This is not a rare edge case — it is the single most-cited gotcha in Python, and it follows directly from a rule most people learn about defaults only after being bitten by it once.

Why it happens

A def statement runs exactly once, when the module is loaded — not once per call. Everything in the signature, including basket=[], is evaluated at that moment, and the resulting list object is stored on the function itself as its default. Every call that omits basket receives that same object, not a fresh copy of it:

python
add_item.__defaults__   # ([...],) — one list, reused forever

A default value that is an immutable type — a number, a string, None, a tuple of immutables — cannot expose this, because there is no way to mutate it in place; any operation on it produces a new object instead of changing the shared one. The bug is specific to mutable defaults: lists, dicts, sets, and any custom object with methods that mutate it.

The fix

python
def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

Now the default stored on the function is None — immutable, harmless to share — and a fresh list is created inside the body on every call that needs one. This is not a workaround; it is the idiom. Seeing =None followed by an if x is None: x = ... line at the top of a function is the normal, expected shape of a Python function that wants a mutable default, and its absence in a code review is usually worth a comment.

The same rule bites class attributes too

python
class Basket:
    items = []   # class attribute — one list shared by every instance

    def add(self, item):
        self.items.append(item)

a = Basket()
b = Basket()
a.add('apple')
b.items   # ['apple'] — b never called add, but shares a's list

items = [] at class body level is evaluated once, exactly like a default argument, and every instance that has not set its own items reads the same list off the class. The fix has the same shape as the function version — assign the mutable value inside __init__ instead, where it runs once per instance:

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

Why this rule surprises people used to other languages

Most languages that support default parameter values re-evaluate the default expression on every call — items=[] in one of those languages really does mean "a new empty list, every time." Python evaluates default expressions once, at definition time, for every type of default without exception. The behaviour is entirely consistent — a number default and a list default follow the identical rule — but only the mutable case has any way to reveal it, which is why the bug reads as list-specific when it is really a definition-time-evaluation rule applied uniformly.

Spotting it without running the code

The signal is a default value written with brackets, braces, or a constructor call that returns a mutable object — =[], ={}, =set(), =SomeClass() — anywhere in a function or method signature. None of those are safe as a literal default the moment the function is expected to be called more than once with that default in play.

Now practice it

1 challenge · 2 quizzes

Reading this page is the cheap half. These are the exercises that make you use it.