How to Define Functions with Multiple Defaults in Python
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.
# ✅ Correct: Required arguments first, then defaults
def create_profile(username, role="user", is_active=True):
print(f"User: {username}, Role: {role}, Active: {is_active}")
# Calling with various combinations
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: False
You cannot define a non-default argument after a default argument (e.g., def func(a=1, b)). This will raise a SyntaxError.
The 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
# ⛔️ Incorrect: The empty list [] is created once at definition
def append_to_list(value, my_list=[]):
my_list.append(value)
return my_list
# First call works as expected
print(append_to_list(1)) # Output: [1]
# Second call REUSES the modified list from the first call!
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.
# ✅ Correct: Use None as a sentinel value
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]
Always use None for default values if the parameter is a list, dictionary, set, or custom object instance.
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
# ⛔️ Incorrect: Timestamp is frozen at definition time
def log_frozen(message, timestamp=datetime.now()):
print(f"{timestamp}: {message}")
# ✅ Correct: Timestamp is calculated when the function runs
def log_dynamic(message, timestamp=None):
if timestamp is None:
timestamp = datetime.now()
print(f"{timestamp}: {message}")
# Demonstration
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 call
Using 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.
# The '*' forces specific arguments to be keyword-only
def configure_server(host="localhost", *, port=8080, debug=False):
print(f"Host: {host}, Port: {port}, Debug: {debug}")
# ✅ Correct: 'port' and 'debug' must be named
configure_server("127.0.0.1", port=3000)
# ⛔️ Incorrect: Treating 'port' as positional raises TypeError
# configure_server("127.0.0.1", 3000)
Output:
Host: 127.0.0.1, Port: 3000, Debug: False
Conclusion
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.