*args and **kwargs
- Accept variable numbers of positional args with
*args - Accept variable keyword args with
**kwargs - Unpack collections into function calls
- Design flexible function APIs
Decorators, functools.wraps, and framework hooks (Flask view functions, pytest fixtures) all rely on *args, **kwargs forwarding. Getting unpacking wrong is the #1 source of "missing 1 required positional argument" errors in wrapper code.
- Mixing positional and keyword unpacking order —
f(*args, **kwargs)must come after named params in the signature. - Passing a dict to
*argsinstead of**kwargs— you'll iterate keys silently, not values. - Forgetting
/and*markers for positional-only or keyword-only params — costs you API clarity in libraries.
*argscollects extra positional arguments into a tuple.**kwargscollects extra keyword arguments into a dict.
Forwarding
fn(*args, **kwargs) is how every decorator and wrapper passes arguments through transparently.
The reverse — unpacking
fn(*[1, 2, 3]) unpacks a list as positional args.
fn(**{"a": 1}) unpacks a dict as keyword args.
Try it
- Write a
max_of(*nums)that returns the largest. - Write a
build_url(host, **params)that returnshost?key=value&....
3 graded exercises
Write the code, click Check. We’ll tell you exactly what to fix.
- Exercise 1
Write a variadic function
sum_all(*nums)that returns the sum of any number of arguments. Then printsum_all(1, 2, 3, 4)andsum_all(10, 20). Expected:10,30. - Exercise 2
Write
build_url(base, **params)that returnsbase + "?" + "&".join(f"{k}={v}" for k, v in sorted(params.items())). Test withbuild_url("/search", q="python", page=2)— expected:/search?page=2&q=python. - Exercise 3
Write a wrapper
logged(fn)that returns a function which prints"calling {fn.__name__}"before invokingfn(*args, **kwargs)and returns its result. Apply it to a functionadd(a, b): return a + b. Then printlogged(add)(3, 4). Expected output includescalling addand7.