*args and **kwargs in Python: Flexible Function Signatures Explained
*argscollects extra positional arguments into a tuple.**kwargscollects extra keyword arguments into a dict.- The names
argsandkwargsare conventions; the*and**are what matter. - Use
*iterableand**mappingat the call site to unpack sequences and dicts into arguments.
*args: variable positional arguments
Prefix a parameter name with * in a function definition and Python collects all extra positional arguments into a tuple under that name:
def add(*numbers):
return sum(numbers)
add(1, 2) # 3
add(1, 2, 3, 4, 5) # 15
add() # 0 (empty tuple)
You can have regular parameters before *args. Those are filled first by position; everything remaining goes into the tuple:
def log(level, *messages):
prefix = f"[{level.upper()}]"
for msg in messages:
print(prefix, msg)
log("info", "server started", "port 8080")
# [INFO] server started
# [INFO] port 8080
**kwargs: variable keyword arguments
Prefix with ** to collect all extra keyword arguments into a dict:
def configure(**settings):
for key, value in settings.items():
print(f" {key} = {value!r}")
configure(host="localhost", port=5432, ssl=True)
# host = 'localhost'
# port = 5432
# ssl = True
Like *args, regular parameters can appear before **kwargs and will be filled first:
def create_user(username, email, **profile):
user = {"username": username, "email": email}
user.update(profile)
return user
create_user("alice", "[email protected]", age=30, role="admin")
# {"username": "alice", "email": "[email protected]", "age": 30, "role": "admin"}
Combining *args and **kwargs
A function can accept both; positional first, keyword after:
def debug(*args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)
debug(1, 2, name="test", verbose=True)
# Args: (1, 2)
# Kwargs: {"name": "test", "verbose": True}
The conventional order in a full signature is: regular params, *args, keyword-only params, **kwargs.
Unpacking at the call site
The * and ** operators also work in reverse at a call site: they unpack a sequence or mapping into individual arguments:
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
args = ("World",)
kwargs = {"greeting": "Hi"}
greet(*args) # "Hello, World!"
greet(**kwargs, name="Bob") # "Hi, Bob!"
greet(*args, **kwargs) # "Hi, World!"
This is frequently used to forward unknown arguments down to another function without explicitly naming them:
numbers = [5, 1, 3, 9, 2]
print(*numbers) # "5 1 3 9 2" (unpacks into print's *args)
coords = [3.0, 4.0]
distance = math.dist(*coords, [0, 0]) # unpacks two arguments
Forwarding arguments through wrappers
Decorators typically use both to forward all arguments to the wrapped function unchanged:
from functools import wraps
import time
def timed(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs) # forward everything
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.3f}s")
return result
return wrapper
Keyword-only parameters
A parameter appearing after a bare * in a signature can only be passed by name, not by position. This is useful for options that have sensible defaults but should not be passed accidentally:
def fetch(url, *, timeout=30, retries=3):
...
fetch("https://example.com") # uses defaults
fetch("https://example.com", timeout=10) # explicit keyword
fetch("https://example.com", 10) # TypeError: positional not allowed
When to use each
| Pattern | Use case |
|---|---|
*args | Functions like max(), print(), math operations that accept 1 to N values of the same kind |
**kwargs | Config builders, wrappers that accept options they pass on without knowing them in advance |
Unpacking *list | Spreading a list into positional args; merging lists in an expression |
Unpacking **dict | Merging dicts; passing a stored parameter set to a function call |
Bare * in signature | Forcing callers to use keyword syntax for clarity on functions with many options |
def f(*args: int, **kwargs: str) means each positional arg is an int and each keyword value is a str. The annotation applies to individual items, not the tuple or dict itself.