*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:

Try it — edit and run

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:

Try it — edit and run

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:

Try it — edit and run

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:

Try it — edit and run

*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.

Check your understanding
1/5

What data type does *args collect its values into?

Exercise · Graded

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 **.