devnotes.

Default Arguments, *args, and **kwargs

Python functions support default parameter values and can accept a flexible number of extra positional and keyword arguments.

def greet(name, greeting="Hello"):    # default value used if not supplied
    return f"{greeting}, {name}!"

greet("Rahim")                # "Hello, Rahim!"
greet("Karim", "Hi")            # "Hi, Karim!"

def total(*args):                 # *args collects extra positional args into a tuple
    return sum(args)

total(1, 2, 3, 4)                 # 10

def describe(**kwargs):            # **kwargs collects extra keyword args into a dict
    for key, value in kwargs.items():
        print(f"{key}: {value}")

describe(name="Rahim", age=25)

def mutable_default(items=[]):      # ⚠️ classic Python trap — see below
    items.append(1)
    return items

*args gathers any number of extra positional arguments into a tuple inside the function, and **kwargs similarly gathers extra keyword arguments into a dict — together they let a function accept a genuinely flexible signature, commonly seen in wrapper/decorator functions that need to forward arbitrary arguments to another function untouched (def wrapper(*args, **kwargs): return original(*args, **kwargs)).

Mutable default arguments are a famous Python trap: a default value like items=[] is created once, when the function is defined, not each time it's called — every call that doesn't supply its own items shares and mutates the same list across calls, silently accumulating state between unrelated calls. The standard fix is def mutable_default(items=None): items = items if items is not None else [], checking for None and creating a fresh list inside the function body each time.

Default Arguments, *args, and **kwargs in Python | DevNotes