Difficulty: Intermediate
Python provides two special syntaxes for writing functions that accept a variable number of arguments: `*args` and `kwargs`. The `*args` syntax collects any number of positional arguments into a tuple, while `kwargs` collects any number of keyword arguments into a dictionary. These mechanisms make it possible to write highly flexible functions and are used extensively in frameworks, decorators, and wrapper functions.
When you define a function with `*args`, any positional arguments that do not match a named parameter are gathered into a tuple named `args` (the name `args` is convention, not a requirement -- the `*` is what matters). Similarly, `kwargs` collects unmatched keyword arguments into a dictionary. You can use both in the same function, but `*args` must come before `kwargs` in the parameter list. A common pattern is `def wrapper(*args, kwargs)` which forwards all arguments to another function.
Python 3 introduced positional-only parameters (using `/` in the signature) and keyword-only parameters (any parameters defined after `*` or `*args`). Positional-only parameters cannot be passed by name, which is useful when the parameter name is an implementation detail you do not want callers to depend on. Keyword-only parameters must be passed by name, improving readability at the call site for functions with many arguments.
The order of parameters in a function signature follows strict rules: regular positional parameters come first, then `/` for positional-only boundary, then regular parameters, then `*args` or bare `*`, then keyword-only parameters, and finally `kwargs`. Understanding this ordering is essential for reading library source code and for writing robust public APIs.
These features also play an important role in function forwarding and the decorator pattern. When writing a decorator, you typically define an inner wrapper that accepts `*args, kwargs` and passes them through to the decorated function. This ensures the decorator works with any function signature without needing to know the specific parameters in advance.
def total(*args):
"""Sum all provided numbers."""
print(f"Received: {args}")
return sum(args)
print(total(1, 2, 3))
print(total(10, 20, 30, 40, 50))
The `*args` parameter collects all positional arguments into a tuple. Inside the function, `args` is a regular tuple that you can iterate over, index, or pass to built-in functions like `sum()`.
def build_profile(name, kwargs):
"""Build a user profile dictionary."""
profile = {"name": name}
profile.update(kwargs)
return profile
result = build_profile("Alice", age=30, city="NYC", role="Developer")
for key, value in sorted(result.items()):
print(f"{key}: {value}")
The `kwargs` parameter collects all extra keyword arguments into a dictionary. This pattern is common for building configuration objects or forwarding options to other functions.
def log_call(func_name, *args, kwargs):
"""Log a function call with its arguments."""
parts = [repr(a) for a in args]
parts += [f"{k}={v!r}" for k, v in kwargs.items()]
print(f"{func_name}({', '.join(parts)})")
log_call("greet", "Alice", greeting="Hello")
log_call("add", 3, 5)
log_call("configure", timeout=30, retries=3, verbose=True)
Regular parameters come first, then `*args` collects remaining positional arguments, and `kwargs` collects remaining keyword arguments. This is the standard pattern for wrapper and logging functions.
def search(query, /, *, case_sensitive=False, limit=10):
"""Demonstrate positional-only and keyword-only params."""
print(f"Query: {query}")
print(f"Case sensitive: {case_sensitive}")
print(f"Limit: {limit}")
search("python")
print("---")
search("decorators", case_sensitive=True, limit=5)
The `/` marks everything before it as positional-only. The `*` (or `*args`) marks everything after it as keyword-only. This means `query` must be passed by position, while `case_sensitive` and `limit` must be passed by name.
*args, kwargs, Positional-Only Parameters, Keyword-Only Parameters