skip to content

Positional-Only and Keyword-Only

The / and * markers in a signature control which parameters a caller may name and which they must name. The binding order they impose explains most argument errors you will be asked to debug.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

What does a bare `*` in a Python function signature such as `def f(a, *, b)` do?

level: juniorimportance: must knowfreq 55%

answer

  1. A separator, not a parameter
  2. It ends the positional region
  3. Callers must name what follows it
  4. Bare star before the last parameters
  5. PEP 3102 keyword-only, since Python 3.0

basics

~10 s

It closes the positional part of the signature. Every parameter written after the bare * is keyword-only, so the caller must pass it by name: f(1, b=2) works and f(1, 2) raises TypeError.

solid answer

~40 s

The bare `*` is a marker, not a parameter: it takes no argument and has no name. It ends the region that positional arguments can fill, so everything after it is **keyword-only** and must be supplied by name. Passing one too many positionals gives `TypeError: f() takes 1 positional argument but 2 were given`, and omitting a required keyword-only parameter gives the distinct `TypeError: f() missing 1 required keyword-only argument: 'b'`. Authors use it so call sites read as documentation instead of a row of anonymous literals, and so options can later be added or reordered without changing what any existing call means. The standard library does the same — `sorted` is documented as `sorted(iterable, /, *, key=None, reverse=False)`. It has been available since Python 3.0 (PEP 3102).

code

python · 8 lines
python
def connect(host, port, *, timeout=30, retries=3):
    return host, port, timeout, retries

print(connect("db.internal", 5432, timeout=5))
try:
    connect("db.internal", 5432, 5)
except TypeError as e:
    print(e)

go deeper

for a junior

Be ready to read a signature aloud and say which arguments must be named. Remember the shape def f(a, *, b) and that f(1, 2) fails while f(1, b=2) works.

for a middle

Explain the mechanics: the marker consumes no argument, it ends the positional region, and it produces its own distinct TypeError messages. Mention that defaults may appear in any order after it.

for a senior

Show the design judgement: which parameters you make keyword-only in a library you maintain, and why adding the marker later breaks existing positional callers loudly rather than silently.

for a principal

Own the API-stability angle: the positional region is a public promise about ordering, so decide once, per codebase, which parameters are subject and which are options, and encode that convention in review.

## Three kinds of parameter slot Every parameter in a Python `def` has one of three binding kinds. A plain parameter — `a` in `def f(a)` — is **positional-or-keyword**: the caller may fill it either by position or by name. A parameter written after a bare `*` is **keyword-only**: it can be filled only by name. A parameter written before a `/` is **positional-only**: it can be filled only by position. The bare `*` is the marker that opens the keyword-only region. ## The `*` is a separator, not a parameter It has no name and it receives no argument. `def f(*): pass` is a `SyntaxError` ("named arguments must follow bare *"), because a marker with nothing behind it says nothing. A `*args` collector also opens the keyword-only region — anything after it is keyword-only too — but the bare `*` is how you say "keyword-only from here" *without* also agreeing to swallow extra positional arguments. ## What it changes at call time CPython binds a call in a fixed order: positional arguments fill the positional slots left to right, then keyword arguments fill the remaining slots by name. Keyword-only parameters are never part of that positional sequence, so a caller who supplies one more positional argument than the positional region holds sees: ``` TypeError: connect() takes 2 positional arguments but 3 were given ``` Note that the count in the message covers only the parameters *before* the `*`. A required keyword-only parameter that the caller never names produces a different message: ``` TypeError: f() missing 1 required keyword-only argument: 'b' ``` The two messages are worth memorising: the word "keyword-only" in a traceback tells you immediately that the signature has a marker in it and that the fix is to name the argument, not to add another positional. ## Defaults behave differently on each side of the marker In the positional region a parameter without a default may not follow one that has a default — `def f(a=1, b)` is a `SyntaxError`. In the keyword-only region that constraint disappears: `def f(*, a=1, b)` is perfectly legal, because `b` can only ever arrive by name, so no ambiguity about which value lands where is possible. Keyword-only parameters may be required, optional, or a mix in any order. ## Legal ordering with the other marker The full order is positional-only parameters, then `/`, then positional-or-keyword parameters, then `*`, then keyword-only parameters — for example `def f(a, /, b, *, c)`. `/` must come ahead of `*`; `def f(*, a, /, b)` fails to compile with "/ must be ahead of *". ## Why an author reaches for it Three reasons dominate. **Readability**: `resize(image, True, False)` tells a reader nothing, while `resize(image, antialias=True, in_place=False)` tells them everything — the marker makes the readable form the only form. **Evolvability**: parameters after `*` are addressed by name and never by position, so a later release can add an option, reorder options, or change a default without altering the meaning of a single existing call. The positional region is a promise about order; the keyword-only region deliberately makes no such promise. **Wrong-slot safety**: a flag or a tuning number passed one slot to the left is a plausible-looking wrong answer that no test may notice, and the marker converts that into a `TypeError` at the call site. The standard library is written this way on purpose. `sorted` is documented as `sorted(iterable, /, *, key=None, reverse=False)`: one obvious operand that nobody wants to name, and two options that nobody should pass positionally. `dataclasses.dataclass` offers a mode that makes every field of the generated `__init__` keyword-only for exactly the same reason. ## Costs, and the migration trap Keyword-only lengthens call sites, so it is the wrong choice for the one or two arguments that *are* the function's subject — nobody wants to write `sorted(iterable=rows)`. And adding a bare `*` to a function that already has callers is a breaking change: every caller who passed those arguments positionally starts raising `TypeError`. That is a loud failure rather than a silent one, which is the failure you want, but it is still a change to schedule deliberately rather than slip into a patch release. The marker is cheapest when it is designed in from the first version of the signature.

  • Can a keyword-only parameter be required, or must it always carry a default?
    It can be required. In `def f(a, *, b)` the parameter `b` has no default, and omitting it raises `TypeError: f() missing 1 required keyword-only argument: 'b'`. The rule that a defaulted parameter may not precede a non-defaulted one applies only to the positional region, so `def f(*, a=1, b)` is legal too — `b` can only arrive by name, so nothing is ambiguous.
  • Is adding a bare `*` to an already-published function a safe change?
    No. Every caller who passed those arguments positionally begins raising `TypeError` at the call site. The failure is loud rather than silent, which is the better kind, but it is still a compatibility break — schedule it at a major version, or design the marker in before the function has callers.
  • What happens if you write `def f(*): pass`?
    It is a `SyntaxError`: 'named arguments must follow bare *'. The bare `*` only opens a keyword-only region, so at least one named parameter has to follow it. If you also want to accept arbitrary extra positional arguments, you write a `*args` collector instead of the bare marker.

The bare * is the counter in a shop: whatever sits in front of it you can hand over in order, and anything behind it you have to ask for by name.

saying these in an interview costs you the question

  • Thinks the bare `*` collects extra positional arguments
  • Calls it a parameter that receives a value
  • Believes keyword-only parameters must all have defaults
  • Says `f(1, 2)` still works because the marker is advisory
  • Confuses the bare `*` with the `/` positional-only marker

context

open as a page

What does the `/` marker mean in a Python signature like `def f(a, b, /, c)`?

level: middleimportance: should knowfreq 40%

basics

~10 s

Every parameter before the / is positional-only: callers may pass it by position but never by name. f(1, 2, 3) works; f(a=1, b=2, c=3) raises TypeError. Python 3.8 added the syntax via PEP 570.

open as a page

Why does calling `def f(a, b)` as `f(1, a=2)` raise a TypeError?

level: middleimportance: should knowfreq 45%

basics

~20 s

Positional arguments bind first, left to right, so the 1 already fills a. The keyword a=2 then asks for a slot that is taken, and Python raises TypeError: f() got multiple values for argument 'a'.

open as a page

How do `/` and `*` markers stop a Python signature from making promises you cannot keep?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A positional-or-keyword parameter promises callers both a position and a name. / withdraws the name so you can rename freely; * withdraws the position so you can add and reorder options. Both turn future mistakes into TypeError.

open as a page