Default & Keyword Arguments
Default arguments let you make a parameter optional by giving it a fallback value. Combined with keyword arguments, they make function calls more readable and functions more forgiving to use. They also hide one of Python's most notorious beginner traps, which this lesson covers in depth.
Setting Default Values
Assign a value to a parameter in the def line to give it a default. If the caller doesn't supply that argument, the default is used instead.
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Ada") # uses default greeting
greet("Ada", "Welcome back") # overrides default
greet("Ada", greeting="Hi") # override via keywordHello, Ada! Welcome back, Ada! Hi, Ada!
Parameters with defaults must come after parameters without defaults in the function signature — Python won't let you write def greet(greeting="Hello", name):.
Calling with Keyword Arguments for Clarity
When a function has several optional parameters, calling it with keywords makes the intent of each value obvious at the call site, instead of relying on position.
def create_account(username, is_admin=False, is_active=True, max_logins=5):
print(username, is_admin, is_active, max_logins)
# Hard to read — what do True and 10 mean here?
create_account("ada", True, True, 10)
# Much clearer:
create_account("ada", is_admin=True, max_logins=10)ada True True 10 ada True True 10
The Mutable Default Argument Bug
This is one of the most infamous gotchas in Python. It happens because default argument values are created once, when the function is defined — not each time the function is called.
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item("apple")) # ['apple']
print(add_item("banana")) # ['apple', 'banana'] <- surprise!
print(add_item("cherry")) # ['apple', 'banana', 'cherry']['apple'] ['apple', 'banana'] ['apple', 'banana', 'cherry']
The Fix: Use `None` as a Sentinel
The standard fix is to default to None and create a fresh mutable object inside the function body every time it runs.
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
print(add_item("apple")) # ['apple']
print(add_item("banana")) # ['banana'] <- fresh list each time
print(add_item("cherry")) # ['cherry']['apple'] ['banana'] ['cherry']
A common shorthand for this pattern is basket = basket or [], but be careful: this also replaces an empty list, empty string, or 0 passed in deliberately, since those are all falsy. The explicit if basket is None: check is safer and preferred in production code.
Defaults Are Evaluated Once, at Definition Time
The mutable default bug is really a symptom of a more general rule: default values are computed a single time, when Python reads the def statement — not on every call. This applies even to non-mutable expressions.
import time
def log_event(message, timestamp=time.time()):
print(f"[{timestamp}] {message}")
log_event("Server started")
time.sleep(1)
log_event("Still using the OLD timestamp")[1751840000.1234] Server started [1751840000.1234] Still using the OLD timestamp
Quick Reference
Pattern | Safe? | Why |
|---|---|---|
| Unsafe | Same list object reused across all calls |
| Mostly safe | Fresh list each call, but treats falsy inputs as missing |
| Safe | Fresh list each call, only |
| Safe | Immutable defaults (numbers, strings, tuples, |
def record_event(user, event, history=None):
if history is None:
history = {}
history.setdefault(user, []).append(event)
return history
h1 = record_event("ada", "login")
h2 = record_event("grace", "logout")
print(h1)
print(h2)
print(h1 is h2) # False — independent dictionaries{'ada': ['login']}
{'grace': ['logout']}
False