Python Args and Kwargs: Default and Keyword Arguments

How Python matches positional and keyword arguments, why a mutable default is shared between calls, and what args, kwargs, the bare star and the slash do.

  • Course: Python study plan
  • Module: Functions
  • Kind: Lesson
  • Reading time: 15 min
  • Runtime: CPython 3.11

What are *args and **kwargs in Python?

In a Python function signature, *args collects any extra positional arguments into a tuple and **kwargs collects any extra keyword arguments into a dict. The names are conventional; the stars are the syntax. They let a function such as print accept any number of values, and let a wrapper forward options it does not itself understand. In a call, f(*seq) and f(**d) unpack the other way.

Lesson

Python's calling convention is richer than most languages': arguments can be passed by position or by name, parameters can have defaults, a function can accept any number of positional or keyword arguments, and a signature can forbid one style or the other. Every part of that is useful and one part — a mutable default — is the most famous bug in the language. This lesson takes the signature apart piece by piece, gives the ordering rule that ties the pieces together, and shows the unpacking that goes the other way, from a list or dict into a call.

Positional and keyword arguments

def greet(name, greeting):
    return f"{greeting}, {name}!"

greet("Ada", "Hello")                  # positional: matched by order
greet(greeting="Hi", name="Ada")       # keyword: matched by name, any order
greet("Ada", greeting="Hi")            # mixed: positionals first

Positional arguments must come before keyword arguments in a call. Passing the same parameter twice, or a keyword that does not exist, is a TypeError. Keyword arguments make a call readable when the parameters are of the same type — move(x=3, y=4) rather than move(3, 4) — and let you skip defaults in the middle of a signature.

Default values

def power(base, exponent=2):
    return base ** exponent

power(3)         # 9
power(3, 3)      # 27
power(3, exponent=3)

Parameters with defaults must come after those without (def f(a=1, b) is a syntax error). The default expression is evaluated once, when the def runs, and the resulting object is stored with the function and reused by every call that omits the argument. For an immutable default that is invisible. For a mutable one it is the bug:

def append_to(x, xs=[]):      # one list, created at def time, shared by all calls
    xs.append(x)
    return xs

print(append_to(1))           # [1]
print(append_to(2))           # [1, 2]   — not [2]

The idiom is a None sentinel and a fresh object inside:

def append_to(x, xs=None):
    if xs is None:
        xs = []
    xs.append(x)
    return xs

The same applies to {}, set(), and any call whose result is meant to be fresh per invocation (time.time() as a default is evaluated once, at definition).

*args and **kwargs

A parameter named *args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. The names are conventional; the stars are the syntax.

def log(level, *messages, **fields):
    text = " ".join(str(m) for m in messages)
    extras = ", ".join(f"{k}={v}" for k, v in fields.items())
    return f"[{level}] {text} {extras}".rstrip()

log("INFO", "started", "ok", user="ada", pid=42)
# '[INFO] started ok user=ada, pid=42'

*args is how print accepts any number of values and how max(1, 2, 3) works. **kwargs is how a wrapper forwards options it does not itself understand. A function that takes *args, **kwargs and passes them on unchanged is the shape of every decorator (Module 16).

Keyword-only and positional-only parameters

A bare * in the signature makes everything after it keyword-only; a / makes everything before it positional-only:

def connect(host, port, *, timeout=10, retries=3):   # timeout and retries must be named
    ...
connect("db", 5432, timeout=5)
connect("db", 5432, 5)          # TypeError: takes 2 positional arguments but 3 were given

def divmod_(a, b, /):                                # a and b cannot be passed by name
    ...

Keyword-only parameters stop a call like connect("db", 5432, 5, 2) whose meaning nobody can read; they are the right choice for boolean flags and options. Positional-only parameters (3.8) let a library rename its parameters later without breaking callers, and are what most built-ins use (len(obj=xs) is a TypeError).

The full ordering of a signature is: positional-only, /, ordinary, *args or *, keyword-only, **kwargs:

def f(pos_only, /, normal, *args, kw_only, **kwargs): ...

Unpacking at the call

The stars work in the other direction too. f(*seq) spreads a sequence into positional arguments; f(**mapping) spreads a dict into keyword arguments:

point = (3, 4)
move(*point)                           # move(3, 4)

options = {"timeout": 5, "retries": 1}
connect("db", 5432, **options)         # connect("db", 5432, timeout=5, retries=1)

print(*range(5))                       # 0 1 2 3 4
first, *rest = [1, 2, 3]               # the same star in an assignment

A common shape is a function that takes a list and a function that takes separate arguments meeting through a star: max(xs) and max(*xs) agree for two or more elements, but max(*[5]) is max(5) — a TypeError, since an int is not iterable — and an empty list fails with a different error in each form.

Arguments are objects, not copies

Every argument is passed the same way: the parameter name is bound to the same object the caller passed. Nothing is copied. A function can mutate a list or dict it receives and the caller sees it; it cannot rebind the caller's name (Module 2 lesson 4). Say in the docstring which you do — "modifies xs in place" or "returns a new list" — and never both.

Pitfalls

  • A mutable default. Use None and create inside.
  • Positional arguments after keyword ones in a call.
  • Too many positional arguments where keyword-only was meant; * protects.
  • f(xs) versus f(*xs) — passing one list versus its elements.
  • Forgetting that *args is a tuple (immutable) and **kwargs a dict.
  • Shadowing a parameter by assigning to it and then expecting the caller to see the change.

Key takeaways

  • Arguments match by position then by name; keywords make same-typed arguments readable.
  • Defaults are evaluated once at def time — never use a mutable default; use None.
  • *args gathers extra positionals into a tuple, **kwargs extra keywords into a dict; * and / make parameters keyword-only or positional-only.
  • Signature order: positional-only /, ordinary, *args, keyword-only, **kwargs.
  • f(*seq) and f(**dict) unpack into a call; every argument is the caller's object, not a copy.

Common questions

Why is a mutable default argument a bad idea in Python?

A default value is evaluated once, when the def runs, and the same object is reused by every call that omits the argument. With def append_to(x, xs=[]), every call appends to one shared list. Use None as the default and create a fresh list inside: if xs is None: xs = [].

What is the difference between positional and keyword arguments in Python?

Positional arguments are matched to parameters by order, keyword arguments by name and in any order, as in greet(greeting="Hi", name="Ada"). Positional arguments must come first in a call, and passing a parameter twice or naming one that does not exist raises TypeError.

What do * and / mean in a Python function signature?

A bare * makes every parameter after it keyword-only, so def connect(host, port, *, timeout=10) must be called with timeout=5, not a third positional value. A /, added in Python 3.8, makes every parameter before it positional-only, as most built-ins are: len(obj=xs) is a TypeError.

How do I pass a list as arguments to a function in Python?

Unpack it with a star: f(*seq) spreads a sequence into positional arguments, so move(*point) with point = (3, 4) calls move(3, 4). f(**options) spreads a dict into keyword arguments. f(xs) without the star passes the whole list as one argument.

What is the order of parameters in a Python function signature?

Positional-only parameters, then /, then ordinary parameters, then *args or a bare *, then keyword-only parameters, then **kwargs: def f(pos_only, /, normal, *args, kw_only, **kwargs). Among positional parameters, those with defaults must follow those without.

Exercises

Echo the arguments

Write describe(*args, **kwargs) returning a two-line description of what it was called with, then call it by unpacking a list and a dict built from one input line: a token containing = is a keyword argument (name=value, both kept as strings), any other token is positional.

Input: one line of tokens (possibly empty). Output: positional: <values separated by spaces> (or (none)), then keyword: <k=v pairs separated by ", "> (or (none)), in the order given.

1 2 name=ada age=3

prints

positional: 1 2
keyword: name=ada, age=3

The mutable default, fixed

The starter's collect(word, bag=[]) is the classic bug: the default list is created once and shared. Each input line is a group of words; for each line the program calls collect for the first word without passing a bag, and for the following words passes the bag back in, then prints the bag. With the bug, the second line's bag still contains the first line's words. Fix the function with the None idiom so every line starts fresh — do not change the calling code.

Input: lines of words. Output: one line per input line: the words collected, separated by spaces.

a b
c d

prints

a b
c d

In this module: Functions

← Defining functions — def, return and functions as values · Scope and closures — LEGB, global, nonlocal and late binding →