When You Don't Know How Many Arguments There Will Be
Most functions take a fixed number of parameters. But occasionally you need something more flexible — a logger that takes any number of messages, a wrapper function that forwards whatever it received to another function, a plotting function that accepts configuration without knowing exactly which options the caller will use.
Python handles this with two special markers in the parameter list: *args and **kwargs.
*args Collects Positional Arguments
A single asterisk says "pack any extra positional arguments into a tuple":
Inside the function, args is a regular tuple. You can loop over it, index into it, pass it to len(), or slice it.
*args usually appears alongside named parameters:
first grabs the first argument; *rest packs everything after it into a tuple.
**kwargs Collects Keyword Arguments
Two asterisks do the equivalent for keyword arguments, packing them into a dict:
Inside describe, kwargs is a regular dict. The keys are the keyword names as strings.
Using Them Together
You can use both in the same function. The convention is *args before **kwargs:
The full parameter ordering, from left to right, is:
- Regular positional parameters (required or with defaults).
*args.- Keyword-only parameters (anything after
*argsmust be passed by keyword). **kwargs.
title grabs the first positional. The rest of the positionals go into tags. draft has to be passed by keyword (it came after *tags). Any other keyword arguments land in metadata.
Unpacking With * and ** at the Call Site
The stars also work in reverse — spreading a sequence or dict into the arguments of a call:
This is incredibly handy for forwarding arguments:
wrapped doesn't need to know what arguments log expects. It just collects everything and forwards it through. This pattern shows up constantly in decorators (a more advanced topic) and wrapper functions.
When *args and **kwargs Are the Wrong Choice
It's easy to get over-excited and use them everywhere. Two warnings:
They hide what a function expects
If every function in your codebase is def f(*args, **kwargs), calling code has no idea what arguments are valid. Use named parameters whenever you can, and let *args/**kwargs carry only genuinely variadic inputs or pure forwarding.
Error messages get vague
A mistyped keyword name becomes a silent None or a KeyError deep inside the function, instead of an immediate "unexpected keyword argument" at the call site. Named parameters give you much better feedback.
As a rule: prefer named parameters by default, and reach for *args/**kwargs only when the function is genuinely flexible or is forwarding arguments to another callable.
A Small Practical Example
A plotting-style helper that wraps a third-party function with a few defaults:
The *values lets callers pass any number of items; **style swallows extra configuration without requiring every option to be a named parameter. It's flexible without being opaque, because the inner logic tells you exactly what keys it reads from style.
Recap
*argspacks extra positional arguments into a tuple.**kwargspacks extra keyword arguments into a dict.- At the call site,
*seqand**dictunpack in the other direction. - Parameters must appear in the order: normal →
*args→ keyword-only →**kwargs. - Don't overuse — named parameters are clearer when you can use them.
Next up: lambda, a way to write tiny throwaway functions inline.
Frequently Asked Questions
What is *args in Python?
*args collects any extra positional arguments into a tuple. def f(*args): lets you call f(1, 2, 3) and receive args as (1, 2, 3). The name args is a convention — you could call it anything, but *args is what everyone uses.
What is **kwargs in Python?
**kwargs collects any extra keyword arguments into a dict. def f(**kwargs): lets you call f(name='Ada', age=30) and receive kwargs as {'name': 'Ada', 'age': 30}. Together, *args and **kwargs let a function accept any combination of arguments.
Do I have to name them args and kwargs?
No, the stars are what matter, not the names. *values and **options work identically. But args and kwargs are a near-universal convention in Python code — stick with them unless there's a specific reason to pick something more descriptive.