Python Function Argument Unpacking with *args and **kwargs
python function argument unpacking: Learn how to use *args and **kwargs for flexible function definitions and how to unpack iterables and mappings when calling functions.
Python Function Argument Unpacking: *args and **kwargs
When you see *args and **kwargs in a Python function definition, you're looking at argument unpacking. The same * and ** syntax also works in function calls, letting you expand a list or dictionary into individual arguments. This feature is central to writing flexible functions and clean call sites.
How Unpacking Works in Function Definitions
In a function definition, *args collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dictionary. The names args and kwargs are conventional, not required.
def log(message, *args, **kwargs): print(message) for a in args: print("arg:", a) for k, v in kwargs.items(): print(f"{k}={v}")
Calling log("start", 1, 2, retries=3) passes 1 and 2 as a tuple to args, and {"retries": 3} to kwargs. This is how Python functions accept a variable number of arguments without knowing them in advance.
The order matters: positional arguments come first, then *args, then keyword-only arguments (if any), then **kwargs. Python enforces this at parse time.
Passing Unpacked Arguments to a Function
The reverse operation happens when you call a function. If you have a list or tuple of values, you can unpack them into positional arguments with *. If you have a dictionary, unpack it into keyword arguments with **.
def point(x, y, z): return x + y + z coords = [1, 2, 3] print(point(*coords)) # 6 params = {"x": 1, "y": 2, "z": 3} print(point(**params)) # 6
This pattern is common when forwarding arguments from one function to another, or when a function receives a collection and needs to pass its elements to another API.
Combining *args and **kwargs in a Single Function
Many functions use both to act as a pass-through layer. A decorator, for example, often needs to accept any arguments and forward them unchanged.
def logged(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper
Here *args and **kwargs capture whatever the wrapped function expects, and the * and ** in the call to func expand them back into separate arguments. This works because the tuple and dictionary preserve the original argument types and order.
Common Mistakes and Edge Cases
One frequent mistake is forgetting the * or ** when calling a function with a collection. Passing a list without * treats the list as a single positional argument, which often causes a TypeError if the function expects more arguments.
Another edge case: mixing *args with keyword-only arguments. Python 3 introduced keyword-only arguments after *args, which forces callers to use the parameter name.
def compare(a, b, *, key=None): pass
Here key can only be passed as key=value, not as a positional argument. This is useful for optional flags that should be explicit.
Also, be careful when unpacking a generator. *gen will consume the generator and create a tuple, which can be memory-heavy for large sequences. For small inputs it's fine, but for large data, consider whether you need all elements at once.
Performance and Maintainability Considerations
Unpacking itself has minimal runtime cost. The * and ** operations create a tuple or dictionary when collecting arguments, and they may iterate during expansion. For most code, this overhead is negligible compared to the function body.
The bigger concern is readability. Overusing *args and **kwargs can hide the actual signature of a function, making it harder for callers to know what arguments are expected. Use them when the number of arguments is genuinely variable, such as in decorators, callbacks, or wrappers. For functions with a fixed set of parameters, explicit arguments are clearer and enable better IDE support and type checking.
Advanced Patterns: Unpacking with Type Hints and in Decorators
Type hints work with *args and **kwargs by annotating the types of the individual elements. For example:
def process(*args: int, **kwargs: str) -> None: pass
This says that args contains integers and kwargs contains string values. The *args itself is a tuple, but the annotation applies to each element.
In decorators, unpacking is essential for preserving the wrapped function's signature. Without *args and **kwargs, a decorator would only work for functions with a specific number of parameters. The flexible signature also allows the decorator to be reused across different callables.
Another advanced use is in function composition or partial application. Libraries like functools.partial internally use similar mechanisms, but you can also manually unpack when building a new function call from a set of parameters.