Why are default values shared between objects in Python?

Default values in Python are created only once when a function is defined, not each time the function is called. This behavior can cause unexpected issues when using mutable objects (like lists or dictionaries) as default parameters. Understanding this concept is crucial for writing reliable Python code.

The Problem with Mutable Defaults

When you use a mutable object as a default parameter, all function calls share the same object. Here's a demonstration of the problem:

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

# First call
result1 = add_item("apple")
print("First call:", result1)

# Second call
result2 = add_item("banana")
print("Second call:", result2)

# Third call with explicit list
result3 = add_item("cherry", ["orange"])
print("Third call:", result3)
First call: ['apple']
Second call: ['apple', 'banana']
Third call: ['orange', 'cherry']

Notice how the second call contains both "apple" and "banana" because both calls shared the same default list object.

Why This Happens

Python evaluates default arguments only once at function definition time, not at each function call. The default value becomes part of the function object and persists between calls.

def problematic_function(data={}):
    return data

# These all reference the same dictionary
call1 = problematic_function()
call2 = problematic_function()

print("Same object?", call1 is call2)
print("ID of call1:", id(call1))
print("ID of call2:", id(call2))
Same object? True
ID of call1: 140234567890432
ID of call2: 140234567890432

The Solution: Use None as Default

The standard solution is to use None as the default value and create a new mutable object inside the function:

def add_item_fixed(item, target_list=None):
    if target_list is None:
        target_list = []
    target_list.append(item)
    return target_list

# First call
result1 = add_item_fixed("apple")
print("First call:", result1)

# Second call
result2 = add_item_fixed("banana")
print("Second call:", result2)
First call: ['apple']
Second call: ['banana']

Legitimate Use Case: Memoization

Sometimes the shared default behavior is actually useful, such as for caching expensive computations:

def fibonacci(n, _cache={}):
    if n in _cache:
        return _cache[n]
    
    if n < 2:
        result = n
    else:
        result = fibonacci(n-1) + fibonacci(n-2)
    
    _cache[n] = result
    return result

# Test the memoized function
print("fib(10):", fibonacci(10))
print("fib(15):", fibonacci(15))
print("Cache size:", len(fibonacci.__defaults__[0]))
fib(10): 55
fib(15): 610
Cache size: 16

Safe vs Unsafe Default Types

Safe (Immutable) Unsafe (Mutable)
None, int, float, str list, dict, set
tuple, frozenset class instances
bool bytearray

Conclusion

Always use None as the default value when you need a mutable object parameter. Create the actual mutable object inside the function to ensure each call gets a fresh instance. Only use mutable defaults intentionally for patterns like caching.

Updated on: 2026-03-26T21:42:22+05:30

2K+ Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements