In Python, default arguments allow you to make function parameters optional, providing a preset value if the caller does not supply one. This feature is essential for creating flexible APIs and reducing the need for multiple function overloads.
However, defining functions with multiple defaults comes with specific syntactic rules and a common "gotcha" regarding mutable objects (like lists or dictionaries). This guide explores the correct syntax, the mutable default trap, and advanced patterns for robust function design.
Basic Syntax and Rules
To define a function with multiple default arguments, you simply assign values to parameters in the function definition.
The Golden Rule: Non-default arguments (positional) must come before default arguments.
def create_profile(username, role="user", is_active=True):
print(f"User: {username}, Role: {role}, Active: {is_active}")
create_profile("alice")
create_profile("bob", role="admin")
create_profile("charlie", is_active=False)Output:
User: alice, Role: user, Active: True
User: bob, Role: admin, Active: True
User: charlie, Role: user, Active: FalseThe Mutable Default Argument Trap
The most common error in Python function definition occurs when using mutable objects (like list or dict) as default values.
Why it happens: Default argument values are evaluated only once when the function is defined, not every time the function is called. If you modify that object, the change persists across future calls.
Error
def append_to_list(value, my_list=[]):
my_list.append(value)
return my_list
print(append_to_list(1)) # Output: [1]
print(append_to_list(2)) # Output: [1, 2] (Expected [2])The Solution: The None Pattern
To fix this, use None as the default value and initialize the mutable object inside the function.
def append_to_list(value, my_list=None):
if my_list is None:
my_list = [] # Created fresh every time the function runs
my_list.append(value)
return my_list
print(append_to_list(1)) # Output: [1]
print(append_to_list(2)) # Output: [2]Advanced Strategies: Dynamic Defaults
Sometimes you want a default value to be dynamic, such as the current timestamp. If you put datetime.now() in the definition line, the time will be "frozen" at the moment the script loads.
The Dynamic Default Pattern
Similar to the mutable fix, use None to indicate that a value should be generated at runtime.
from datetime import datetime
import time
def log_frozen(message, timestamp=datetime.now()):
print(f"{timestamp}: {message}")
def log_dynamic(message, timestamp=None):
if timestamp is None:
timestamp = datetime.now()
print(f"{timestamp}: {message}")
log_dynamic("First call")
time.sleep(1)
log_dynamic("Second call")Output:
2025-12-24 21:52:36.020852: First call
2025-12-24 21:52:37.020988: Second callUsing Keyword-Only Arguments
When a function has many default arguments, it can become confusing to pass values positionally. You can force users to use keyword arguments by placing an asterisk * in the parameter list.
def configure_server(host="localhost", *, port=8080, debug=False):
print(f"Host: {host}, Port: {port}, Debug: {debug}")
configure_server("127.0.0.1", port=3000)Output:
Host: 127.0.0.1, Port: 3000, Debug: FalseConclusion
To define functions with multiple defaults effectively:
- Order Matters: Place non-default arguments before default arguments.
- Avoid Mutable Defaults: Never use
[]or{}as a default. UseNoneand initialize inside the function. - Dynamic Values: Use
Nonefor defaults that need to be calculated at runtime (like timestamps). - Keyword-Only: Use
*to force clarity when your function accepts many configuration options.