codejump

AcademyPython

Args and kwargs in Python

How the star and double-star collectors turn extra positional and keyword arguments into a tuple and a dict, and where positional-only and keyword-only parameters fit.

3 min readUpdated

3 exercises

Waiting at the end of this page.

What the two collectors actually hold

In a function signature, a parameter written with a single leading star collects every extra positional argument into a tuple; one written with two leading stars collects every extra keyword argument into a dict. The names args and kwargs are convention, not syntax — the star is what matters:

python
def describe(*sizes, **labels):
    print(sizes)   # a tuple
    print(labels)  # a dict

describe(1, 2, 3, small='s', large='l')
# (1, 2, 3)
# {'small': 's', 'large': 'l'}

Nothing forces a function to take both — either can appear alone, and both can sit alongside ordinary named parameters, as long as ordering rules are respected: named parameters first, then the star collector, then keyword-only parameters, then the double-star collector.

Unpacking is the same star, run in reverse

The same syntax that collects arguments on the receiving side spreads a tuple or dict back out on the calling side:

python
def add(a, b, c):
    return a + b + c

values = (1, 2, 3)
add(*values)          # unpacks the tuple into three positional arguments

settings = {'a': 1, 'b': 2, 'c': 3}
add(**settings)       # unpacks the dict into three keyword arguments

This is how a decorator's wrapper(*args, **kwargs) forwards whatever it received straight through to the wrapped function without knowing its signature — it collects on the way in and unpacks on the way out, and both sides use the identical two symbols.

Why a function reaches for this at all

The usual reason is not "take unlimited arguments" for its own sake — it is forwarding a call through a layer that should not need to know the target's exact signature. A logging decorator, a class method that delegates to a parent's constructor, or a wrapper around a library function that might gain new keyword arguments in a future version all use *args, **kwargs for the same reason: the wrapper's job is to pass everything along unchanged, not to enumerate it.

Positional-only and keyword-only parameters

A bare star in the middle of a signature, with nothing after it collecting, marks everything after it as keyword-only — it must be passed by name:

python
def resize(image, *, width, height):
    ...

resize(img, width=100, height=50)   # fine
resize(img, 100, 50)                # TypeError — width and height are keyword-only

A slash does the opposite — everything before it must be passed positionally:

python
def divide(a, b, /):
    return a / b

divide(10, 2)      # fine
divide(a=10, b=2)  # TypeError — a and b are positional-only

Both exist for the same reason: an API's author decides which parameter names are part of the public contract. width and height being keyword-only means a caller's code reads resize(img, width=100, height=50) instead of two unlabelled numbers whose order has to be remembered, and divide's parameters being positional-only means the library is free to rename a and b in a later version without breaking anyone who called it by position.

The mutable default trap this pattern avoids

A parameter collected by **kwargs is a fresh dict on every call, which sidesteps a related and much more common bug: giving an ordinary parameter a mutable object as its default value. That default is created once, when the function is defined, and every call that does not override it shares that same object — a separate concept worth its own page, but the reason def f(items=None) is the idiom, never def f(items=[]).

Now practice it

1 challenge · 2 quizzes

Reading this page is the cheap half. These are the exercises that make you use it.