Codectionary / Developer documentation / Python

Default arguments and mutable values

Understand when defaults are created and avoid accidentally sharing state between calls.

How it works

A default expression is evaluated when the def statement executes, not every time the function is called. Repeated calls that omit that argument reuse the same default object.

Immutable defaults such as numbers and strings are usually straightforward. A mutable list or dictionary can accumulate changes across calls. A None sentinel allows the body to create a fresh container for each omitted argument.

Choices and common mistakes

Use is None rather than a truthiness check when an explicitly provided empty container should be respected.

The sentinel pattern prevents accidental sharing for omitted arguments; it does not prevent intentional mutation of a caller-provided container. Document that separately.

Try it yourself

Write a function that appends a tag to a supplied list or creates a new list when none is supplied. Verify both independent calls and an explicitly shared list.

Syntax

def collect(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

Examples

See the shared-list mistake

Observe why a mutable default can retain earlier results.

def collect(item, items=[]):
    items.append(item)
    return items

print(collect("a"))
print(collect("b"))
  1. The empty list is created once when def executes. Both calls omit items and therefore use that same list.
  2. append mutates the shared object. The second result includes a from the earlier call rather than starting empty.
  3. This is an intentional bug demonstration; do not use this signature for independent request data.
  4. Expected output: ['a'] ['a', 'b']

Create a fresh list per call

Use a sentinel to choose when allocation happens.

def collect(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

print(collect("a"))
print(collect("b"))
  1. Each omitted argument is None. The body creates a new list on each call, so the results do not share storage.
  2. The return value is the list after appending. The default itself is never mutated.
  3. Use this for independent batches, but document that a supplied list will be modified.
  4. Expected output: ['a'] ['b']

See definition-time evaluation

Distinguish a default value from a live variable lookup.

limit = 3
def take(items, count=limit):
    return items[:count]

limit = 1
print(take([10, 20, 30]))
print(take([10, 20, 30], count=limit))
  1. count captures the integer three when def executes. Rebinding limit later does not replace that saved default.
  2. The second call explicitly evaluates the current limit and supplies one, overriding the default.
  3. Pass changing configuration explicitly rather than expecting defaults to refresh themselves.
  4. Expected output: [10, 20, 30] [10]

Best practices

  • Use is None rather than a truthiness check when an explicitly provided empty container should be respected.
  • The sentinel pattern prevents accidental sharing for omitted arguments; it does not prevent intentional mutation of a caller-provided container. Document that separately.

Runtime & versions

Python interpreter. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

Python language reference

Specifications & further reading