*args & **kwargs

RoadmapsPython Backend

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') # messages = ('start', 'processing', 'done') **kwargs collects any extra keyword arguments into a DICT: def create_record(**fields): return fields create_record(name='alice', role='admin') # fields = {'name': 'alice', 'role': 'admin'} Both can be combined in one signature: def f(*args, **kwargs). The * and ** can also be used at the call site to UNPACK a sequence or dict into positional/keyword arguments: params = {'host': 'localhost', 'port': 5432} connect(**params) # same as connect(host='localhost', port=5432) 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. `*args` packs extra positionals into a tuple, `**kwargs` packs extra keywords into a dict -- consistently, regardless of the call site.

Wrapper functions, decorators, and logging helpers must accept any combination of arguments to forward them. *args/**kwargs are the tool 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: Mutable vs immutable types

Next: Positional vs keyword arguments

Return to Python Backend Roadmap