*args and **kwargs
The problem: a function with an unknown number of arguments
Everything so far assumes you know the parameters in advance. But sometimes you don’t — especially when writing code that forwards calls to something else, which comes up constantly in agent code (a wrapper that logs a tool call, then passes it through unchanged).
def add(a, b):
return a + b
# What if you wanted a version that works with any number of arguments?
# add(1, 2, 3, 4) — this signature can't support that*args — variable positional arguments
*args collects any number of positional arguments into a tuple:
args is just a name by convention, not a keyword; *numbers would work
identically. The * is what matters — it tells Python “collect any extra
positional arguments here.”
**kwargs — variable keyword arguments
**kwargs does the same thing for keyword arguments, collecting them into
a dict:
This is exactly the
dict iteration pattern you saw when working with dicts
— .items() unpacked as key, value — applied here to arguments the
function didn’t know in advance it would receive.
Combining regular parameters with both
The order is fixed: regular parameters, then *args, then **kwargs:
Why this matters for agent code: forwarding calls
The most common real use isn’t “accept unlimited arguments” for its own sake — it’s writing a wrapper that doesn’t need to know a function’s exact signature to pass a call through it:
*args and **kwargs also work in reverse, at the call site:
func(*args, **kwargs) unpacks the tuple and dict back out into individual
arguments, rather than passing them as one tuple and one dict. This pattern
— wrap any function, forward its call, add behavior around it — is exactly
the shape of tool-calling middleware you’ll build later in this course.
What data type does *args collect its values into?
Merge default_kwargs and kwargs into one dict (with kwargs values winning on any shared key), then call func with *args and the merged dict unpacked as **.