*args & **kwargs

Python lets a function accept any number of extra positional and keyword arguments, collecting them into a tuple and a dict. It's the mechanism behind flexible APIs and decorators — and the */** symbols do double duty: they collect in a definition and spread at a call site.

positional vs keyword*args → tuple **kwargs → dictdefaults unpacking

How a call binds to parameters

When you call a function, Python matches the arguments to the parameters in a fixed order: fill the named parameters left-to-right from the positional arguments, apply any defaults for ones you skipped, then sweep up whatever's left over. A *args parameter catches all extra positional arguments as a tuple; a **kwargs parameter catches all extra keyword arguments as a dict. Pick a call and watch each argument land in its slot:

def order(item, qty=1, *extras, **opts):

The same symbols, the other direction: unpacking

At a call site, * and ** do the reverse — they spread an existing sequence or dict into separate arguments. order(*["Mocha", 3]) is identical to order("Mocha", 3); order(**{"size": "M"}) is identical to order(size="M"). This symmetry is why you'll constantly see the pattern def wrapper(*args, **kwargs): return func(*args, **kwargs) — collect whatever came in, then pass it straight through. That one line is the heart of how decorators wrap arbitrary functions.

args = ("Mocha", 3)
opts = {"size": "M"}
order(*args, **opts)  # → order("Mocha", 3, size="M")

Naming & good taste

The names args and kwargs are pure convention — only the * and ** matter to Python, so *coordinates or **options are perfectly legal and often clearer. Use them when the count genuinely varies (a print-like API, a pass-through wrapper). Don't reach for them when a couple of explicit, named parameters would document the function better — *args, **kwargs everywhere makes a signature say nothing about what it actually wants.

One related trap lives next door: a mutable default argument like def f(x, acc=[]) is evaluated once and shared across calls — see names & objects. Use None as the default and create the list inside.

Takeaways: calls bind positional args left-to-right, fill defaults for skipped params, then *args collects extra positional args into a tuple and **kwargs collects extra keyword args into a dict. The same */** at a call site unpack a list/dict into arguments — the basis of pass-through wrappers and decorators.

Watch the tuple and dict get built during a call in Python Tutor.