Variable-Length Arguments
*args and **kwargs let a function accept any number of positional or keyword arguments, turning fixed signatures into flexible, forwardable ones.
Sometimes you don't know in advance how many arguments a function needs to accept — a print-like function, a mathematical sum_all, or a wrapper that forwards whatever it receives to another function. Python solves this with *args (collects extra positional arguments into a tuple) and **kwargs (collects extra keyword arguments into a dict). The same */** syntax also *unpacks* collections back into individual arguments at a call site, making packing and unpacking two sides of one mechanism.
*args: Packing Positional Arguments
A parameter prefixed with * collects any extra positional arguments into a tuple. The name args is convention, not a keyword — any valid identifier works, but args is what everyone expects.
def total(*numbers):
return sum(numbers)
print(total(1, 2, 3)) # 6
print(total(10, 20)) # 30
print(total()) # 0 — numbers is an empty tuple
print(type(total.__code__.co_varnames)) # 'numbers' is bound to a tuple inside the function**kwargs: Packing Keyword Arguments
A parameter prefixed with ** collects any extra keyword arguments into a dict, keyed by the argument names used at the call site.
Because dicts preserve insertion order (a language guarantee since 3.7), **kwargs iterates in the order the caller supplied the keyword arguments.
def describe_pet(**traits):
for key, value in traits.items():
print(f"{key}: {value}")
describe_pet(name="Milo", species="cat", age=3)
# name: Milo
# species: cat
# age: 3Combining Fixed, *args, and **kwargs
A single signature can mix required parameters, *args, and **kwargs — in that order. This lets a function require certain arguments while still accepting arbitrary extras.
def build_url(base, *path_parts, **query_params):
path = "/".join(path_parts)
query = "&".join(f"{k}={v}" for k, v in query_params.items())
url = f"{base}/{path}"
return f"{url}?{query}" if query else url
print(build_url("https://api.example.com", "users", "42", verbose="true", page=2))
# https://api.example.com/users/42?verbose=true&page=2Packing: What Goes Into args and kwargs
'Packing' is the *def*-side behavior: extra positional arguments are gathered ('packed') into the *args tuple, and extra keyword arguments are packed into the **kwargs dict, automatically, by Python's call machinery.
def show(*args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
show(1, 2, x=3, y=4)
# args: (1, 2)
# kwargs: {'x': 3, 'y': 4}Unpacking: The Reverse Direction at a Call Site
'Unpacking' is the *call*-side behavior: * spreads an existing iterable into separate positional arguments, and ** spreads an existing mapping into separate keyword arguments.
def add3(a, b, c):
return a + b + c
values = [1, 2, 3]
print(add3(*values)) # unpack list -> 3 positional args -> 6
options = {"a": 10, "b": 20, "c": 30}
print(add3(**options)) # unpack dict -> 3 keyword args -> 60
# Multiple unpacking is allowed (3.5+):
more = [4, 5, 6]
print([*values, *more]) # [1, 2, 3, 4, 5, 6]
merged = {**options, "d": 40}
print(merged) # {'a': 10, 'b': 20, 'c': 30, 'd': 40}Forwarding Arguments
A very common pattern is a wrapper function that accepts *args, **kwargs purely to forward them, untouched, to another function — used heavily in decorators, subclass __init__ methods calling super().__init__, and logging/proxy wrappers.
import functools
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
return func(*args, **kwargs) # forward everything unchanged
return wrapper
@log_call
def add(a, b):
return a + b
print(add(2, 3))
# Calling add with args=(2, 3), kwargs={}
# 5Practical Usage
*args/**kwargs show up constantly: variadic math functions (min, max-style), decorators that must work on any wrapped function, class hierarchies where a subclass forwards unknown keyword arguments to its parent, and configuration functions accepting an open-ended set of options.
class Base:
def __init__(self, name, **options):
self.name = name
self.options = options
class Widget(Base):
def __init__(self, name, color="blue", **kwargs):
super().__init__(name, **kwargs) # forward whatever Base still needs
self.color = color
w = Widget("button", color="red", size="large", disabled=False)
print(w.color, w.options) # red {'size': 'large', 'disabled': False}Edge Cases
Unpacking an object that isn't iterable (for *) or isn't a mapping with string keys (for `) raises a TypeError. Duplicate keys between kwargs` unpacking and an explicit keyword argument raise 'got multiple values'.
def f(a, b):
return a + b
f(*5) # TypeError: argument after * must be an iterable
f(**{1: "x"}) # TypeError: keywords must be strings
f(a=1, **{"a": 2}) # TypeError: got multiple values for keyword argument 'a'Mental Model
Think of * and ** as a single 'spread/gather' operator that changes direction depending on where it appears: in a def, it *gathers* loose arguments into a container; in a call, it *spreads* a container back out into loose arguments.
Quick Reference
In a def: *args -> tuple of extra positional args
**kwargs -> dict of extra keyword args
At a call: f(*seq) -> spread seq as positional args
f(**mapping) -> spread mapping as keyword args
Order in a def: required, *args, keyword-only, **kwargsKey Takeaways
*args packs extra positional arguments into a tuple inside the function; **kwargs packs extra keyword arguments into a dict.
The same */** syntax at a call site does the opposite: it unpacks a sequence or mapping back into individual arguments.
Parameters after a bare *args become keyword-only automatically.
*args, **kwargs wrapper signatures are the standard way to forward arbitrary arguments through decorators and subclass constructors.
Unpacking a non-iterable with *, or a non-string-keyed mapping with **, raises a TypeError.
def wrapper(*args, **kwargs): return func(args, kwargs) # forgot to unpack again def wrapper(*args, **kwargs): return func(*args, **kwargs)
Passing the packed tuple and dict directly (without re-unpacking with * and **) sends func exactly two arguments — a tuple and a dict — instead of forwarding the original arguments.
def f(a, **kwargs, *args): ...
def f(a, *args, **kwargs): ...
**kwargs must always be the last item in a parameter list; *args (if present) comes before it. The wrong order is a SyntaxError.
def total(*args): return sum(args) total([1, 2, 3]) # passes ONE list, not three numbers total(*[1, 2, 3]) # unpack the list into three separate arguments
Passing a list directly makes args a one-tuple containing that list, not three separate numbers; unpacking with * at the call site spreads it correctly.
What does *args collect, and what data type does it become inside the function?
It collects every extra positional argument the caller supplies beyond the named parameters, and binds them as a tuple inside the function body.
What does **kwargs collect, and what data type does it become inside the function?
It collects every extra keyword argument the caller supplies beyond the named parameters, and binds them as a dict (keyed by argument name) inside the function body.
What is the difference between 'packing' and 'unpacking' with * and **?
Packing happens in a function definition: * or ** gathers loose extra arguments into a tuple or dict. Unpacking happens at a call site: * or ** spreads an existing sequence or mapping back out into individual arguments.
In def f(a, *args, b, **kwargs), is b positional or keyword-only, and why?
b is keyword-only, because any parameter listed after *args in a signature automatically becomes keyword-only — the caller must pass b=value.
Why do decorator wrapper functions almost always use def wrapper(*args, **kwargs)?
Because a decorator doesn't know the signature of the function it's wrapping in advance, so *args, **kwargs lets the wrapper accept literally any call and forward it unchanged with func(*args, **kwargs).
What error do you get from f(*5) if f is a normal function, and why?
A TypeError, because * requires an iterable to unpack, and an int (5) is not iterable.
How would you merge two dictionaries into a new one using unpacking syntax?
merged = {dict1, dict2} — later keys override earlier ones on conflict, since dict literals apply key-value pairs left to right.
Write a function multiply_all(*numbers) that returns the product of any number of arguments (1 if none are given).
from functools import reduce def multiply_all(*numbers): return reduce(lambda a, b: a * b, numbers, 1) print(multiply_all(2, 3, 4)) print(multiply_all())
24 then 1 — the product of 2*3*4 is 24, and the identity value 1 is returned when no arguments are given.
What does this print?
1 2 (3, 4) {'x': 5, 'y': 6} — a and b take the first two positional args, the rest go to args, and named ones go to kwargs.
Write a function safe_call(func, *args, **kwargs) that calls func(*args, **kwargs) and returns None instead of raising if any exception occurs.
def safe_call(func, *args, **kwargs): try: return func(*args, **kwargs) except Exception: return None print(safe_call(int, "42")) print(safe_call(int, "not a number"))
42 then None — the first call succeeds and forwards '42' to int(); the second raises ValueError internally, which safe_call catches and converts to None.
{'x': 1, 'y': 2} — both dicts can be unpacked into the same call as long as their keys don't overlap; overlapping keys would raise a TypeError.
def f(a, b, *args, **kwargs):
print(a, b, args, kwargs)
f(1, 2, 3, 4, x=5, y=6)def f(**kwargs):
return kwargs
a = {"x": 1}
b = {"y": 2}
print(f(**a, **b))