Designing clear function signatures — and reading library ones — needs all the parameter kinds.
Beyond plain parameters, a Python signature can declare several kinds of parameter:
*argscollects extra positional arguments into a tuple**kwargscollects extra keyword arguments into a dict- parameters after
*(or after*args) are keyword-only: callers must name them - parameters before
/are positional-only: callers can't name them
The full order is def f(pos_only, /, normal, *args, kw_only, **kwargs).
Keyword-only parameters are a great design tool. def connect(host, *, timeout=10, retries=3) forces call sites to say timeout=5, which keeps them readable and lets you reorder or add options later without breaking anyone. Positional-only parameters do the reverse: they let you rename a parameter freely, and many built-ins use them (len(obj, /)).
The same stars unpack at the call site: f(*items) spreads a sequence into positional arguments, and f(**options) spreads a dict into keyword arguments. Together they enable the forwarding idiom, where a wrapper accepts *args, **kwargs and passes them straight through to another function — the foundation of decorators.
*args and **kwargs are conventional names; only the stars matter. Use them when a function genuinely accepts a variable number of things (like print) or forwards arguments. Don't use **kwargs just to avoid writing a signature: explicit parameters are self-documenting, checked by Python, and understood by your editor and type checker.
Example
def log(message, *values, level="INFO", **context):
extra = " ".join(f"{k}={v}" for k, v in context.items())
print(f"[{level}] {message}", *values, extra)
log("started") # [INFO] started
log("sum is", 3, 4, level="DEBUG", user="ana")
# [DEBUG] sum is 3 4 user=ana
def connect(host, port=5432, /, *, timeout=10):
return f"{host}:{port} (timeout={timeout})"
print(connect("db", 6543, timeout=2)) # db:6543 (timeout=2)
# connect(host="db") -> TypeError: positional-only
# connect("db", 1, 2) -> TypeError: timeout is keyword-only
point = (3, 4)
opts = {"sep": " | ", "end": "!\n"}
print(*point, **opts) # 3 | 4!
def wrapper(*args, **kwargs): # forward everything
return connect(*args, **kwargs)
print(wrapper("api", timeout=1)) # api:5432 (timeout=1)Remember
*argsis a tuple of extra positionals,**kwargsa dict of extra keywords- Parameters after
*are keyword-only; before/are positional-only f(*seq, **mapping)unpacks at the call site
Watch out for
- Hiding a function's real options behind
**kwargs, so typos are silently ignored.