Your greet() function usually says "Hello" but sometimes needs "Hi" or "Hey". Default arguments let callers omit parameters when the default is fine, while still allowing customization when needed.

Basic default values

Give parameters default values.

greet.py
def main():
    print("=== Default Arguments ===\n")
    
    # Without default - must provide both
    greet_formal("Alice", "Good morning")
    
    # With default - greeting is optional
    greet("Bob")                    # Uses default "Hello"
    greet("Charlie", "Hey")         # Overrides default
    greet("Diana", "Good evening")
    
    print("\n=== Common Pattern ===")
    # Default makes common case simple
    # Special cases still possible
    
    greet("Everyone")  # 90% of calls use default
    greet("VIP", "Welcome, dear")  # 10% need custom

def greet_formal(name, greeting):
    """Both arguments required."""
    print(f"{greeting}, {name}!")

def greet(name, greeting="Hello"):
    """greeting has default value."""
    print(f"{greeting}, {name}!")

def greet_polite(name, greeting="Hello", punctuation="!"):
    """Multiple defaults."""
    print(f"{greeting}, {name}{punctuation}")

if __name__ == "__main__":
    main()

Parameters with defaults can be omitted when calling the function.

default argument Parameter with preset value: `def f(x=10):`. Used if caller doesn't provide.

Keyword arguments skip positional defaults

Use keyword syntax to specify later parameters.

keyword_args.py
def main():
    print("=== Keyword Arguments ===\n")
    
    # Positional: order matters
    print("Positional order:")
    describe_pet("Hamster", "Harry")
    describe_pet("Dog", "Buddy")
    
    # Keyword: name=value, order doesn't matter
    print("\nKeyword arguments:")
    describe_pet(pet_name="Whiskers", animal_type="Cat")
    describe_pet(animal_type="Fish", pet_name="Nemo")     # Reversed!
    
    # Mix positional and keyword
    print("\nMixed:")
    describe_pet("Rabbit", pet_name="Thumper")
    
    # Skip middle defaults with keywords!
    print("\nSkipping defaults:")
    format_text("hello world", uppercase=True)  # Skip width
    format_text("hello world", width=20)        # Skip uppercase
    format_text("hello world", uppercase=True, width=15)  # All specified

def describe_pet(animal_type, pet_name):
    """Describe a pet."""
    print(f"  I have a {animal_type} named {pet_name}.")

def format_text(text, width=0, uppercase=False):
    """Format text with optional width and case."""
    result = text.upper() if uppercase else text
    if width > 0:
        result = result.center(width)
    print(f"  '{result}'")

if __name__ == "__main__":
    main()

func(c=30) skips a and b if they have defaults.

Multiple default parameters

Functions can have several parameters with defaults.

multiple_defaults.py
def main():
    print("=== Multiple Default Arguments ===\n")
    
    # Using all defaults
    print("All defaults:")
    make_coffee()
    
    # Override some
    print("\nCustom orders:")
    make_coffee(size="large")
    make_coffee(milk=True)
    make_coffee(sugar=2)
    make_coffee(size="small", sugar=3)
    
    # Override all
    print("\nFully customized:")
    custom_size = 
    sugar_count = 
    make_coffee(custom_size, True, sugar_count)  # positional
    make_coffee(size=custom_size, milk=True, sugar=sugar_count)  # keyword
    
    # Configuration pattern
    print("\n=== Configuration Pattern ===")
    print_report()
    print_report(title="Sales Report", columns=3)
    print_report(show_header=True, title="Status")

def make_coffee(size="medium", milk=False, sugar=0):
    """Make coffee with customizable options."""
    order = f"  {size.capitalize()} coffee"
    if milk:
        order += " with milk"
    if sugar > 0:
        order += f", {sugar} sugar(s)"
    print(order)

def print_report(title="Report", columns=2, show_header=False):
    """Print a configurable report."""
    if show_header:
        print(f"  === {title} ===")
    else:
        print(f"  {title}")
    print(f"  ({columns} columns)")

if __name__ == "__main__":
    main()
def main():
    print("=== Multiple Default Arguments ===\n")
    
    # Using all defaults
    print("All defaults:")
    make_coffee()
    
    # Override some
    print("\nCustom orders:")
    make_coffee(size="large")
    make_coffee(milk=True)
    make_coffee(sugar=2)
    make_coffee(size="small", sugar=3)
    
    # Override all
    print("\nFully customized:")
    custom_size = 
    sugar_count = 
    make_coffee(custom_size, True, sugar_count)  # positional
    make_coffee(size=custom_size, milk=True, sugar=sugar_count)  # keyword
    
    # Configuration pattern
    print("\n=== Configuration Pattern ===")
    print_report()
    print_report(title="Sales Report", columns=3)
    print_report(show_header=True, title="Status")

def make_coffee(size="medium", milk=False, sugar=0):
    """Make coffee with customizable options."""
    order = f"  {size.capitalize()} coffee"
    if milk:
        order += " with milk"
    if sugar > 0:
        order += f", {sugar} sugar(s)"
    print(order)

def print_report(title="Report", columns=2, show_header=False):
    """Print a configurable report."""
    if show_header:
        print(f"  === {title} ===")
    else:
        print(f"  {title}")
    print(f"  ({columns} columns)")

if __name__ == "__main__":
    main()
def main():
    print("=== Multiple Default Arguments ===\n")
    
    # Using all defaults
    print("All defaults:")
    make_coffee()
    
    # Override some
    print("\nCustom orders:")
    make_coffee(size="large")
    make_coffee(milk=True)
    make_coffee(sugar=2)
    make_coffee(size="small", sugar=3)
    
    # Override all
    print("\nFully customized:")
    custom_size = 
    sugar_count = 
    make_coffee(custom_size, True, sugar_count)  # positional
    make_coffee(size=custom_size, milk=True, sugar=sugar_count)  # keyword
    
    # Configuration pattern
    print("\n=== Configuration Pattern ===")
    print_report()
    print_report(title="Sales Report", columns=3)
    print_report(show_header=True, title="Status")

def make_coffee(size="medium", milk=False, sugar=0):
    """Make coffee with customizable options."""
    order = f"  {size.capitalize()} coffee"
    if milk:
        order += " with milk"
    if sugar > 0:
        order += f", {sugar} sugar(s)"
    print(order)

def print_report(title="Report", columns=2, show_header=False):
    """Print a configurable report."""
    if show_header:
        print(f"  === {title} ===")
    else:
        print(f"  {title}")
    print(f"  ({columns} columns)")

if __name__ == "__main__":
    main()
def main():
    print("=== Multiple Default Arguments ===\n")
    
    # Using all defaults
    print("All defaults:")
    make_coffee()
    
    # Override some
    print("\nCustom orders:")
    make_coffee(size="large")
    make_coffee(milk=True)
    make_coffee(sugar=2)
    make_coffee(size="small", sugar=3)
    
    # Override all
    print("\nFully customized:")
    custom_size = 
    sugar_count = 
    make_coffee(custom_size, True, sugar_count)  # positional
    make_coffee(size=custom_size, milk=True, sugar=sugar_count)  # keyword
    
    # Configuration pattern
    print("\n=== Configuration Pattern ===")
    print_report()
    print_report(title="Sales Report", columns=3)
    print_report(show_header=True, title="Status")

def make_coffee(size="medium", milk=False, sugar=0):
    """Make coffee with customizable options."""
    order = f"  {size.capitalize()} coffee"
    if milk:
        order += " with milk"
    if sugar > 0:
        order += f", {sugar} sugar(s)"
    print(order)

def print_report(title="Report", columns=2, show_header=False):
    """Print a configurable report."""
    if show_header:
        print(f"  === {title} ===")
    else:
        print(f"  {title}")
    print(f"  ({columns} columns)")

if __name__ == "__main__":
    main()
def main():
    print("=== Multiple Default Arguments ===\n")
    
    # Using all defaults
    print("All defaults:")
    make_coffee()
    
    # Override some
    print("\nCustom orders:")
    make_coffee(size="large")
    make_coffee(milk=True)
    make_coffee(sugar=2)
    make_coffee(size="small", sugar=3)
    
    # Override all
    print("\nFully customized:")
    custom_size = 
    sugar_count = 
    make_coffee(custom_size, True, sugar_count)  # positional
    make_coffee(size=custom_size, milk=True, sugar=sugar_count)  # keyword
    
    # Configuration pattern
    print("\n=== Configuration Pattern ===")
    print_report()
    print_report(title="Sales Report", columns=3)
    print_report(show_header=True, title="Status")

def make_coffee(size="medium", milk=False, sugar=0):
    """Make coffee with customizable options."""
    order = f"  {size.capitalize()} coffee"
    if milk:
        order += " with milk"
    if sugar > 0:
        order += f", {sugar} sugar(s)"
    print(order)

def print_report(title="Report", columns=2, show_header=False):
    """Print a configurable report."""
    if show_header:
        print(f"  === {title} ===")
    else:
        print(f"  {title}")
    print(f"  ({columns} columns)")

if __name__ == "__main__":
    main()
def main():
    print("=== Multiple Default Arguments ===\n")
    
    # Using all defaults
    print("All defaults:")
    make_coffee()
    
    # Override some
    print("\nCustom orders:")
    make_coffee(size="large")
    make_coffee(milk=True)
    make_coffee(sugar=2)
    make_coffee(size="small", sugar=3)
    
    # Override all
    print("\nFully customized:")
    custom_size = 
    sugar_count = 
    make_coffee(custom_size, True, sugar_count)  # positional
    make_coffee(size=custom_size, milk=True, sugar=sugar_count)  # keyword
    
    # Configuration pattern
    print("\n=== Configuration Pattern ===")
    print_report()
    print_report(title="Sales Report", columns=3)
    print_report(show_header=True, title="Status")

def make_coffee(size="medium", milk=False, sugar=0):
    """Make coffee with customizable options."""
    order = f"  {size.capitalize()} coffee"
    if milk:
        order += " with milk"
    if sugar > 0:
        order += f", {sugar} sugar(s)"
    print(order)

def print_report(title="Report", columns=2, show_header=False):
    """Print a configurable report."""
    if show_header:
        print(f"  === {title} ===")
    else:
        print(f"  {title}")
    print(f"  ({columns} columns)")

if __name__ == "__main__":
    main()

All defaults must come after non-default parameters.

The mutable default trap

Never use mutable objects as defaults!

mutable_trap.py
def main():
    print("=== ⚠️ Mutable Default Trap ===\n")
    
    # Watch carefully!
    print("Calling add_item 3 times with defaults:")
    
    result1 = add_item_broken("apple")
    print(f"Call 1: {result1}")
    
    result2 = add_item_broken("banana")
    print(f"Call 2: {result2}")  # Surprise!
    
    result3 = add_item_broken("cherry")
    print(f"Call 3: {result3}")  # Even worse!
    
    print("\n=== What Happened? ===")
    print("Default list is created ONCE at function definition.")
    print("Every call shares the SAME list object!")
    print("Each append adds to that shared list.")
    
    print("\n=== With Explicit Lists ===")
    # These work because we provide our own lists
    my_list = []
    add_item_broken("dog", my_list)
    print(f"My list: {my_list}")
    
    other_list = []
    add_item_broken("cat", other_list)
    print(f"Other list: {other_list}")
    
    print("\n=== Same Problem with Dict ===")
    print(add_settings_broken("theme", "dark"))
    print(add_settings_broken("volume", "100"))  # Both settings!

# ❌ BROKEN: Mutable default
def add_item_broken(item, items=[]):
    items.append(item)
    return items

# ❌ BROKEN: Dict default
def add_settings_broken(key, value, settings={}):
    settings[key] = value
    return settings

if __name__ == "__main__":
    main()

Defaults are evaluated once at definition time. Mutable defaults persist!

mutable default trap `def f(x=[]):` - the list is created once and reused. Bug waiting to happen.

Use None to avoid the trap

The standard pattern to avoid mutable default issues.

none_pattern.py
def main():
    print("=== None Pattern: Safe Defaults ===\n")
    
    # Correct way with lists
    print("Using None pattern:")
    
    result1 = add_item("apple")
    print(f"Call 1: {result1}")
    
    result2 = add_item("banana")
    print(f"Call 2: {result2}")  # Fresh list!
    
    result3 = add_item("cherry")
    print(f"Call 3: {result3}")  # Still fresh!
    
    print("\n=== Providing Your Own List ===")
    my_list = ["existing"]
    result = add_item("new", my_list)
    print(f"With my list: {result}")
    
    print("\n=== Pattern Comparison ===")
    print("❌ def func(items=[]):    - Broken!")
    print("✅ def func(items=None):  - Safe!")
    
    # Dictionary pattern
    print("\n=== None Pattern with Dict ===")
    config1 = build_config(debug=True)
    print(f"Config 1: {config1}")
    
    config2 = build_config(verbose=True)
    print(f"Config 2: {config2}")  # Independent!

# ✅ CORRECT: None default
def add_item(item, items=None):
    if items is None:
        items = []  # Fresh list each time!
    items.append(item)
    return items

# ✅ CORRECT: Dict with None
def build_config(base_config=None, **settings):
    if base_config is None:
        base_config = {}  # Fresh dict each time!
    base_config.update(settings)
    return base_config

if __name__ == "__main__":
    main()

Use None as default, then create the mutable object inside the function.

None pattern `def f(x=None): x = x or []` - safe way to have mutable-like defaults.

Exercise: sentinel.py

Explore sentinel objects for distinguishing None from 'not provided'