Skip to content

How to Define Functions with Multiple Defaults in Python

Learn Python's rules for multiple default arguments, avoid the mutable default trap, and implement dynamic defaults with None for robust function design.

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: False

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

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 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.

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: False

Conclusion

To define functions with multiple defaults effectively:

  1. Order Matters: Place non-default arguments before default arguments.
  2. Avoid Mutable Defaults: Never use [] or {} as a default. Use None and initialize inside the function.
  3. Dynamic Values: Use None for defaults that need to be calculated at runtime (like timestamps).
  4. Keyword-Only: Use * to force clarity when your function accepts many configuration options.
Navigation