Python Args Kwargs Together: Syntax and Usage
python args kwargs together: Learn how to combine *args and **kwargs in Python function definitions, including ordering rules, unpacking, and practical examples.
Combining *args and **kwargs in a single Python function definition is a common requirement when you need to accept an arbitrary number of positional and keyword arguments. The syntax is straightforward, but the ordering rules and behavior under the hood often trip up developers. This article explains how to use python args kwargs together correctly, with practical examples and the reasoning behind each rule.
The Syntax for Combining *args and **kwargs
In a function definition, *args collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dictionary. When used together, *args must appear before **kwargs. This is not a style choice; it is enforced by the Python parser. The following signature is valid:
def collect(*args, **kwargs): print("Positional:", args) print("Keyword:", kwargs)
Any positional arguments beyond the explicitly named parameters go into args. Any keyword arguments not matched to a named parameter go into kwargs. The names args and kwargs are conventional but not required; you can use any valid identifier after the asterisks.
How Arguments Are Assigned
When you call a function that uses both *args and **kwargs, Python first assigns values to any explicit parameters, then places the remaining positional arguments into the args tuple, and the remaining keyword arguments into the kwargs dictionary. Consider this function:
def show(a, b, *args, **kwargs): print("a:", a) print("b:", b) print("args:", args) print("kwargs:", kwargs) show(1, 2, 3, 4, x=5, y=6)
Output:
a: 1
b: 2
args: (3, 4)
kwargs: {'x': 5, 'y': 6}
The explicit parameters a and b consume the first two positional arguments. Everything else positional goes to args, and all unmatched keyword arguments go to kwargs. This ordering is deterministic and consistent across Python 3 versions.
Unpacking and Calling Functions
The same syntax works in reverse when calling a function. You can unpack an iterable into positional arguments with * and a mapping into keyword arguments with **. This is especially useful when you need to forward arguments from one function to another:
def wrapper(*args, **kwargs): return target(*args, **kwargs)
This pattern preserves the original call exactly. The wrapper receives a tuple and a dict, then unpacks them back into the target function. The target sees the same arguments as if the caller had invoked it directly.
You can also use * and ** in a call without defining a function that accepts them:
def add(a, b, c): return a + b + c values = [1, 2, 3] print(add(*values)) # 6 mapping = {'a': 1, 'b': 2, 'c': 3} print(add(**mapping)) # 6
Combining both in a single call is allowed, but the order matters: positional unpacking must come before keyword unpacking, and keyword arguments cannot follow ** unpacking.
Common Patterns and Use Cases
The most frequent use of *args and **kwargs together is in decorators and wrapper functions. A decorator needs to accept any arguments the wrapped function might receive and pass them through unchanged:
import functools def logged(func): @functools.wraps(func) def inner(*args, **kwargs): print("Calling", func.__name__) return func(*args, **kwargs) return inner
Another pattern is subclassing or overriding methods where the base class signature may change over time. Using *args and **kwargs in the override lets you accept a superset of arguments without breaking existing callers.
You also see this in framework code, such as Flask route handlers or Django class-based views, where the framework calls your function with a variable set of parameters. The combined form gives you a flexible catch-all for both positional and named arguments.
Ordering Rules and Restrictions
Beyond the basic *args before **kwargs rule, Python 3 also supports keyword-only arguments. These appear after *args but before **kwargs. For example:
def f(a, *args, b, **kwargs): pass
Here b is keyword-only: it must be passed as a keyword argument, not as a positional one. This gives you fine-grained control over which arguments are positional, which are keyword-only, and which are captured by the catch-alls.
There are restrictions you cannot violate. You cannot have two *args or two **kwargs in the same signature. You cannot place *args after **kwargs. You cannot use a bare * without a following keyword-only parameter. These rules are part of the language grammar, so the interpreter raises a SyntaxError at definition time rather than at runtime.
Readability and Maintainability Concerns
Using *args and **kwargs together makes a function signature opaque. Callers cannot see what arguments are expected without reading the documentation or the function body. This is a tradeoff. For internal helpers and thin wrappers, the flexibility often outweighs the loss of clarity. For public APIs, you should prefer explicit parameters and reserve *args and **kwargs for genuinely variable inputs.
Type hints can mitigate some of the ambiguity. You can annotate args as a tuple of a specific type and kwargs as a dict with string keys:
def process(*args: int, **kwargs: str) -> None: ...
However, this only describes the types of the collected values, not the names or meaning of individual keyword arguments. If you need precise typing, consider using a Protocol or a TypedDict for the keyword arguments instead.
Runtime Behavior and Performance
Collecting arguments into a tuple and a dict adds a small amount of overhead compared to a function with a fixed signature. For most applications this is negligible. The cost is proportional to the number of arguments, so a function called millions of times with dozens of arguments may show measurable impact. If performance is critical, measure before optimizing; the overhead is rarely the bottleneck.
Memory usage also scales with the number of arguments. Each call creates a new tuple and dict, which are garbage-collected after the call. In long-running loops, this can increase allocation pressure. If you are writing a hot path, prefer explicit parameters when the argument count is known and bounded.
Edge Cases and Pitfalls
One common mistake is forgetting that *args collects positional arguments only, and **kwargs collects keyword arguments only. If you call a function with a list where a keyword argument is expected, you get a TypeError. Similarly, passing a dictionary with non-string keys to **kwargs raises an error because keyword argument names must be strings.
Another pitfall is accidentally shadowing an explicit parameter. If you define def f(a, *args, **kwargs) and then call f(1, a=2), the keyword a is rejected because a is already bound positionally. Python raises TypeError: got multiple values for argument 'a'. This happens even though a is not in kwargs; the parser catches the conflict.
When forwarding arguments, be careful not to double-unpack. If you already have a tuple of positional arguments and a dict of keyword arguments, passing *args and **kwargs is correct. But if you accidentally pass *args as a single positional argument, the target receives one tuple instead of multiple arguments. This is a frequent source of subtle bugs in wrapper code.
Finally, remember that *args and **kwargs do not enforce any relationship between the two. You can pass any combination of positional and keyword arguments. If your logic requires that certain keys appear in kwargs, you must validate them manually inside the function. There is no built-in mechanism to require a specific keyword argument when using **kwargs.
For most real-world code, combining *args and **kwargs is a pragmatic way to build flexible interfaces. The key is to use them deliberately, document the expected arguments, and avoid overusing them in public APIs where an explicit signature would be clearer.