Python *args and **kwargs
Learn how *args and **kwargs let Python functions accept any number of positional and keyword arguments, with real examples and common patterns.
*args and **kwargs are special syntax in Python that let a function accept a variable number of arguments. *args collects extra positional arguments into a tuple, while **kwargs collects extra keyword arguments into a dictionary. Together they give you total flexibility — you can write functions that work with one argument or one hundred.
This page covers both features in depth: how they work, when to use them, how to combine them, and the common pitfalls to avoid.
What Is *args?
When you prefix a parameter name with a single asterisk (*), Python collects all extra positional arguments passed to the function into a tuple bound to that parameter name. The name args is a convention — you could write *numbers or *values — but *args is universally understood.
def add_all(*args):
total = 0
for n in args:
total += n
return total
print(add_all(1, 2, 3)) # 6
print(add_all(10, 20, 30, 40)) # 100
print(add_all()) # 0Inside the function, args is an ordinary tuple that you can loop over, index, or pass to other functions. Calling add_all() with zero arguments is valid — args is simply an empty tuple.
Mixing regular parameters with *args
Regular (positional) parameters come first; *args catches everything that follows:
def greet(greeting, *names):
for name in names:
print(greeting + ', ' + name + '!')
greet('Hello', 'Alice', 'Bob', 'Charlie')
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!greeting is filled by the first argument; names receives the rest as a tuple. If you call greet('Hi') with no extra names, names is an empty tuple and the loop simply does not run — no error.
What Is **kwargs?
Two asterisks (**) before a parameter name tell Python to collect all extra keyword arguments into a dictionary. Again, kwargs is convention; any valid Python identifier works.
def describe(**kwargs):
for key, value in kwargs.items():
print(key + ': ' + str(value))
describe(name='Alice', age=30, city='New York')
# name: Alice
# age: 30
# city: New YorkInside the function, kwargs is an ordinary dictionary. You can loop over it, look up keys, or pass it on. The caller decides which keys to supply — none of them are fixed by the function definition.
When to reach for **kwargs
**kwargs shines when:
- A function needs to accept a flexible, open-ended set of named options (configuration, metadata, HTML attributes).
- You are writing a wrapper that must forward keyword arguments to another function without knowing what they are.
- You want to build a dictionary from keyword arguments in a readable way (avoids the boilerplate of
dict(key=value, ...)).
Combining *args and **kwargs
A single function can accept unlimited positional arguments and unlimited keyword arguments. The required order in the signature is:
- Normal positional parameters
*args- Keyword-only parameters (with defaults)
**kwargs
def log_event(event, *tags, **metadata):
print('Event:', event)
print('Tags:', tags)
print('Metadata:', metadata)
log_event('login', 'auth', 'user', user_id=42, ip='127.0.0.1')
# Event: login
# Tags: ('auth', 'user')
# Metadata: {'user_id': 42, 'ip': '127.0.0.1'}event takes the first positional argument; tags catches the remaining positional arguments; metadata catches all keyword arguments.
Unpacking Arguments with * and **
The * and ** operators are not only for function definitions — they also work on the call side to unpack sequences and mappings into separate arguments.
Unpacking a list or tuple with *
def multiply(a, b, c):
return a * b * c
nums = [2, 3, 4]
print(multiply(*nums)) # 24*nums unpacks the list so that a=2, b=3, c=4. This is equivalent to writing multiply(2, 3, 4). See Unpack Tuples for more on the unpacking operator.
Unpacking a dictionary with **
def power(base, exp):
return base ** exp
params = {'base': 3, 'exp': 4}
print(power(**params)) # 81**params maps each dictionary key to the matching parameter name. This is useful when arguments are stored in a configuration dictionary built at runtime.
Keyword-Only Arguments After *args
Any parameter listed after *args in the signature can only be passed by name (it becomes a keyword-only argument). This is a clean way to add optional flags without ambiguity:
def configure(host, *args, port=80, debug=False):
print('host:', host)
print('extra:', args)
print('port:', port)
print('debug:', debug)
configure('localhost', 'arg1', port=8080, debug=True)
# host: localhost
# extra: ('arg1',)
# port: 8080
# debug: Trueport and debug cannot be set positionally because *args already consumes all positional overflow. This pattern is common in library APIs — users must write port=8080 explicitly, which makes call sites self-documenting.
For a detailed explanation of Python's scoping rules, see Python Scope.
Forwarding Arguments to Another Function
One of the most practical uses of *args/**kwargs is writing wrappers and decorators that pass arguments through to an inner function without knowing what those arguments are:
def add_all(*args):
return sum(args)
def wrapper(*args, **kwargs):
print('Calling with args:', args, 'kwargs:', kwargs)
return add_all(*args)
print(wrapper(1, 2, 3))
# Calling with args: (1, 2, 3) kwargs: {}
# 6This pattern appears throughout Python's standard library and is the foundation of decorators and higher-order functions.
Full Signature Order
Python enforces a strict ordering rule for all parameter kinds. The complete order is:
| Position | Kind | Example |
|---|---|---|
| 1 | Positional-only (Python 3.8+) | a, b, / |
| 2 | Normal positional-or-keyword | x, y |
| 3 | Variable positional | *args |
| 4 | Keyword-only | flag=True |
| 5 | Variable keyword | **kwargs |
Breaking this order is a SyntaxError. A function that uses all five kinds looks like:
def full_sig(pos1, pos2, /, normal, *args, kw_only, **kwargs):
print(pos1, pos2, normal, args, kw_only, kwargs)
full_sig(1, 2, 3, 4, 5, kw_only='k', extra='e')
# 1 2 3 (4, 5) k {'extra': 'e'}In everyday code you rarely need all five at once. The most common patterns are *args alone, **kwargs alone, or *args followed by **kwargs.
Type Annotations
You can annotate *args and **kwargs with type hints. The annotation applies to each individual item, not to the tuple or dict itself:
from typing import Any
def add_all(*args: float) -> float:
return sum(args)
def describe(**kwargs: Any) -> None:
for key, value in kwargs.items():
print(f'{key}: {value}')
print(add_all(1.5, 2.5, 3.0)) # 7.0
describe(name='Bob', score=99)
# name: Bob
# score: 99*args: float means every element of args is expected to be a float. **kwargs: Any means values can be anything. This keeps static-analysis tools happy while preserving runtime flexibility.
Common Pitfalls
1. Wrong argument order in the signature
Putting **kwargs before *args is a SyntaxError:
# Wrong — raises SyntaxError
# def bad(name, **kwargs, *args): ...
# Correct
def good(name, *args, **kwargs):
pass2. Mutating the args tuple
args is a tuple and therefore immutable. If you need to modify the arguments, convert to a list first:
def double_all(*args):
items = list(args) # mutable copy
items = [x * 2 for x in items]
return items
print(double_all(1, 2, 3)) # [2, 4, 6]3. Shadowing a required parameter name
If you use *args and also have a keyword argument with the same name as a positional parameter, callers can get confused. Keep parameter names distinct and use keyword-only parameters (after *args) for optional flags.
4. Over-using **kwargs instead of explicit parameters
**kwargs hides what a function actually accepts, making autocompletion and static analysis harder. Prefer explicit parameters for the options your function genuinely supports; use **kwargs only when the set of options is truly open-ended or when forwarding to another function.
Summary
| Feature | Syntax | What it collects | Type inside function |
|---|---|---|---|
| Variable positional args | *args | Extra positional arguments | tuple |
| Variable keyword args | **kwargs | Extra keyword arguments | dict |
| Unpack sequence on call | func(*seq) | List/tuple → positional args | — |
| Unpack mapping on call | func(**mapping) | Dict → keyword args | — |
For closely related topics, see Python Functions for function basics, Python Lambda for anonymous functions, and Python Scope for how Python resolves variable names.