*args and **kwargs

RoadmapsPython

Overview

`*args` and `**kwargs` let a function accept a variable number of arguments. `*args` collects any extra positional arguments into a tuple: def log(*messages): for m in messages: print(m) log('start', 'processing', 'done') `**kwargs` collects any extra keyword arguments into a dictionary: def create_record(**fields): return fields create_record(name='alice', role='admin') Both can be combined in one signature like `def f(*args, **kwargs)`. The `*` and `**` operators can also be used at the call site to unpack a sequence or dict into positional or keyword arguments: params = {'host': 'localhost', 'port': 5432} connect(**params) Think of `*args` as a tuple-shaped funnel that catches extra positional values, and `**kwargs` as a dict-shaped funnel that catches extra keyword values. They pack extra arguments consistently, regardless of the call site. Watch out for parameter order when defining functions. A regular parameter placed after `*args` automatically becomes a keyword-only argument: def create_user(*roles, status): pass create_user('admin', 'editor', status='active') create_user('admin', 'editor', 'active') # TypeError: create_user() missing 1 required keyword-only argument: 'status' This behavior is useful but often catches beginners by surprise (covered fully in keyword-positional-params).

Wrapper functions, decorators, and logging helpers must accept any combination of arguments to forward them. `*args` and `**kwargs` are the tools for writing these adaptors without hard-coding every parameter.

Where used: `FastAPI` dependency injection wrappers, Logging helper functions, Decorator factories that forward arguments

Why learn this

Code walkthrough

def connect(host, port, **options):
    print(f'connecting to {host}:{port}')
    print(f'options: {options}')

creds = {'timeout': 30, 'ssl': True}
connect('localhost', 5432, **creds)

Focus: `**creds` at the call site unpacks the `dict` into keyword arguments; inside `connect` they are re-collected into the `options` `dict` — showing the unpack/collect round-trip.

Common mistakes

Recall questions

Understanding checks

What is printed by the function call?

<class 'tuple'> <class 'dict'>

`*args` collects positional arguments into a tuple, and `**kwargs` collects keyword arguments into a `dict`.

Why does this code throw a TypeError?

The `params` dictionary is being passed as a positional argument, not unpacked into keyword arguments.

To pass the dictionary as keyword arguments, you must use the `**` operator at the call site: `process_user('Alice', **params)`. Otherwise, `params` is assigned to a positional parameter. Since the function only takes one positional argument (`name`), Python complains that too many positional arguments were given.

Practice tasks

Flexible request logger

The `log_request` function currently only logs a single path string and a single predefined header. Modify the function signature and implementation so it can accept ANY number of path parts as positional arguments (`*args`), which should be joined by `'/'`, and ANY number of headers as keyword arguments (`**kwargs`), which should be printed as `key: value`.

Challenge

Generic function forwarder

Write `timed_call(fn, *args, **kwargs)` that calls `fn(*args, **kwargs)`, prints `'called <fn.__name__>'`, and returns the result. Test it by forwarding calls to the built-in `sorted()` with a list and a `key` function.

Continue learning

Previous: Defining functions and return values

Previous: Mutable vs immutable objects

Next: Keyword-only and positional-only parameters

Next: First-class functions: passing and returning functions

Return to Python Roadmap