Python args kwargs difference: *args vs **kwargs
python args kwargs difference: Learn how *args and **kwargs differ in Python: *args collects positional arguments into a tuple, **kwargs collects keyword arguments int...
When a Python function must accept a variable number of arguments, *args and **kwargs are the two mechanisms that handle it. The python args kwargs difference is straightforward once you see what each collects: *args gathers extra positional arguments into a tuple, and **kwargs gathers extra keyword arguments into a dictionary. That single distinction drives everything else, including how you declare the parameters, how callers pass values, and which one fits a given API design.
What *args Captures
*args collects any positional arguments that were not matched by the explicitly declared parameters. The collected values arrive as a tuple, so they are ordered, immutable, and iterable.
def log(level, *args): print(f"Level: {level}") for arg in args: print(arg) log("INFO", "request started", 200, 12.4)
Here level receives "INFO", and args becomes ("request started", 200, 12.4). The function can iterate over the tuple, pass it to another function, or index into it. Because it is a tuple, you cannot accidentally mutate the collected arguments inside the function, which keeps behavior predictable when the same values are forwarded elsewhere.
What **kwargs Captures
**kwargs collects keyword arguments that were not matched by declared parameters. The values arrive as a dictionary, so each argument keeps its name and can be looked up by key.
def connect(host, port, **kwargs): timeout = kwargs.get("timeout", 30) retries = kwargs.get("retries", 3) print(f"Connecting to {host}:{port} with timeout={timeout}") connect("db.internal", 5432, timeout=15, retries=5)
The keyword arguments timeout and retries are stored in kwargs as {"timeout": 15, "retries": 5}. Using .get() with a default is the common pattern because the caller may omit any of these options. The dictionary preserves the argument names, which is what makes **kwargs suitable for passing named options through a wrapper.
The Names Are Convention, the Asterisks Are Syntax
args and kwargs are not keywords. They are conventional names that the community uses, but Python only cares about the * and ** prefix. You can rename them without changing behavior.
def handle(*values, **options): pass
The parameter name after * is just the local variable that holds the tuple, and the name after ** is the local variable that holds the dictionary. Renaming to something descriptive can improve readability when the collected arguments have a clear meaning, such as *items for a list of items or **headers for HTTP headers.
Combining *args and **kwargs
A function can declare both, and Python fills them independently: positional extras go to *args, keyword extras go to **kwargs.
def request(method, url, *args, **kwargs): print(method, url, args, kwargs) request("GET", "/api", "header1", "header2", timeout=10, cache=False)
args receives ("header1", "header2") and kwargs receives {"timeout": 10, "cache": False}. This combination is common in wrapper functions and decorators that must forward an arbitrary call unchanged.
Parameter Ordering Rules
Python enforces a strict order for parameter kinds in a function signature. The order is:
- Positional-only parameters (before
/) - Positional-or-keyword parameters
*args- Keyword-only parameters (after
*args) **kwargs
def func(a, b, /, c, d, *args, e, f, **kwargs): pass
| Position | Parameter kind | Example |
|---|---|---|
| 1 | Positional-only | a, b, / |
| 2 | Positional-or-keyword | c, d |
| 3 | Variable positional | *args |
| 4 | Keyword-only | e, f |
| 5 | Variable keyword | **kwargs |
Keyword-only parameters are the ones declared after *args; they must be passed by name. This ordering exists so the parser can unambiguously decide whether an argument is positional, collected into *args, or stored in **kwargs. Violating the order raises a SyntaxError at definition time rather than at call time.
Unpacking in Function Calls
The same * and ** syntax works in the opposite direction. When calling a function, * unpacks an iterable into positional arguments, and ** unpacks a mapping into keyword arguments.
def add(a, b, c): return a + b + c values = [1, 2, 3] add(*values) config = {"a": 1, "b": 2, "c": 3} add(**config)
This is how a wrapper forwards arguments without knowing their count. A decorator can capture *args, **kwargs and then re-expand them with func(*args, **kwargs), preserving the original call exactly.
Runtime Behavior and Performance
The runtime cost of *args and **kwargs is small but not zero. Python must build a tuple for *args and a dictionary for **kwargs on every call where extra arguments exist. For most code this overhead is negligible, but in a hot loop that calls a variadic function millions of times, the allocation of the tuple or dict is repeated work that a fixed-signature function avoids.
The tuple from *args is immutable and uses less memory than the dictionary from **kwargs, which must store both keys and values and maintain hash-table structure. If you only need ordered values without names, *args is the lighter choice. If you need named access, **kwargs is the only option.
Common Mistakes and Edge Cases
The most frequent mistake is forgetting the asterisk when forwarding arguments. Passing args without * sends the tuple itself as a single positional argument, and passing kwargs without ** sends the dict as a single positional argument. Both change the call signature and usually produce a TypeError or silently wrong behavior.
Another edge case is an empty collection. A call with no extra positional arguments gives an empty tuple, and a call with no extra keyword arguments gives an empty dict. Code that iterates over args or kwargs handles this naturally, but code that indexes args[0] or kwargs["key"] without checking will fail.
Choosing Between *args and **kwargs
Use *args when the extra values are positional and their meaning is implied by order, such as a list of numbers to sum or a sequence of values to log. Use **kwargs when the extra values are named options that callers may omit, such as configuration flags or HTTP headers.
A wrapper that must forward an arbitrary call unchanged should accept both and re-expand both. A function that only needs a few optional named options should declare them explicitly instead of using **kwargs, because explicit parameters give better error messages, IDE support, and documentation. Reaching for **kwargs as a shortcut for every optional parameter hides the API contract and makes callers guess what names are valid.