skip to content

What do inspect.Parameter.kind values tell you about a callable's parameters?

level: middleimportance: must knowfreq 45%

answer

  1. The name does not tell you how to pass it
  2. One attribute on every Parameter object
  3. An enumeration with exactly five members
  4. POSITIONAL_ONLY through VAR_KEYWORD, in order
  5. No default means empty, not None

basics

~10 s

Each Parameter object from inspect.signature() carries a kind saying how that parameter may be supplied: POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY or VAR_KEYWORD. The name alone never tells you that.

solid answer

~40 s

`inspect.signature(f).parameters` maps each parameter name to a `Parameter`, and `Parameter.kind` is one of five enumeration members: `POSITIONAL_ONLY` (position only, what `/` declares and what most C builtins use), `POSITIONAL_OR_KEYWORD` (the ordinary case), `VAR_POSITIONAL` (the single starred parameter), `KEYWORD_ONLY` (anything after `*`), and `VAR_KEYWORD` (the single double-starred parameter). They appear in that non-decreasing order when you iterate the mapping. The kind is what a generic caller must consult: a value cannot be passed by name to a `POSITIONAL_ONLY` parameter, and an unrecognised keyword is only tolerated if a `VAR_KEYWORD` parameter exists. Separately, `Parameter.default` is `Parameter.empty` — not `None` — when there is no default, since `None` is itself a legitimate default value.

code

python · 8 lines
python
import inspect

def render_invoice(doc, /, template, *sections, dpi=300, **options):
    return doc, template, sections, dpi, options

for name, p in inspect.signature(render_invoice).parameters.items():
    has_default = p.default is not inspect.Parameter.empty
    print(f"{name:9} {p.kind.name:22} default={has_default}")

go deeper

for a junior

Be ready to name the five kinds and say which call syntax each one allows. Knowing that Parameter.default is Parameter.empty rather than None when there is no default is the detail that most often trips people up first.

for a middle

An interviewer expects you to explain the mechanics: how the kinds map onto the / and * markers, why they appear in a fixed non-decreasing order, and why a generic caller must consult kind before deciding whether it may pass a value by name.

for a senior

Show that you have introspected real callables. Talk about bound methods dropping the first parameter, partial application rewriting kinds, and builtins that raise ValueError with no signature at all — and about handling that path instead of assuming every callable is describable.

for a principal

Own the API-evolution angle: which kind you choose for a public parameter fixes what you can change later. Positional-or-keyword freezes the name, positional-only frees it, keyword-only keeps a growing argument list readable, and a catch-all keyword parameter trades early errors for flexibility.

A `Signature` object returned by `inspect.signature()` holds an ordered mapping of `Parameter` objects, one per declared parameter. Each `Parameter` carries four pieces of information: `name`, `default`, `annotation` and — the one that decides how the parameter may actually be *supplied* at a call — `kind`. The name alone is never enough: two parameters called `dpi` in two different functions can be settable by keyword in one and unreachable by keyword in the other. `kind` is the attribute that closes that gap. ## The five kinds `Parameter.kind` is a member of an enumeration with exactly five values, and they are the same five the language itself recognises: - **`POSITIONAL_ONLY`** — supplied by position and never by name. Declared in pure Python with the `/` marker, and the kind you see on most C-implemented callables: `inspect.signature(len)` reports `(obj, /)`. - **`POSITIONAL_OR_KEYWORD`** — the ordinary case, and the default for a plain `def f(a)`. It may be passed either way, which is exactly why adding one is a compatible API change and renaming one is not. - **`VAR_POSITIONAL`** — the single parameter written with one star, which absorbs the leftover positional arguments. At most one exists per signature. - **`KEYWORD_ONLY`** — supplied by name only. Every parameter that follows a `VAR_POSITIONAL` parameter or a bare `*` marker has this kind. - **`VAR_KEYWORD`** — the single double-starred parameter that absorbs leftover keywords. Its presence is the strongest signal a tool can read: the callable will not reject an unrecognised keyword outright. Iterating `signature(f).parameters` yields them in declaration order, and because the language constrains declaration order, the kinds you see are non-decreasing in that same order. That is what makes a simple loop over the mapping meaningful rather than something you have to sort first. ## `Parameter.empty`, not `None` "Has no default" and "defaults to `None`" are different facts, and `None` is a perfectly common default, so `inspect` cannot use it as a sentinel. Instead `Parameter.default` is set to the class attribute `Parameter.empty` when the parameter has no default at all, and the correct test is an identity check against it. The same sentinel is used for a missing annotation. Writing `if p.default is None` is a real bug: it silently reports "no default" for every parameter that genuinely defaults to `None`. ## Why the kind matters in practice The kind is what any generic caller has to consult before it can pass anything. A dispatcher that holds a table of handlers and a bag of configuration values cannot just splat the bag: values whose names match `POSITIONAL_ONLY` parameters are unreachable by keyword and will raise `TypeError`, and names that match nothing at all will raise unless a `VAR_KEYWORD` parameter is present to swallow them. `Signature.bind` applies precisely these rules — it is the introspection-side model of the interpreter's own argument-binding step — so a tool that wants to fail early rather than at the call can lean on it instead of reimplementing the checks. The kinds also explain a class of confusing signatures you will meet while debugging. A bound method reports a signature with `self` already removed, while the same function accessed on the class still shows it as the first `POSITIONAL_OR_KEYWORD` parameter. Pre-supplying an argument through a partial application object rewrites kinds rather than merely dropping parameters: supplying an ordinary parameter by keyword turns it into a `KEYWORD_ONLY` parameter with a default, because after that point no positional argument can reach it. And `inspect.signature` is allowed to fail — some C-implemented builtins carry no introspectable signature at all and raise `ValueError` ("no signature found for builtin"), so production code that introspects arbitrary callables has to handle that rather than assume every callable is describable. ## Reading it, not guessing it The reason to read `kind` from the signature object rather than parse the source or reason from a name is that many callables have no Python source to read: C functions, partial applications, class objects (whose signature is derived from the constructor), objects with a `__call__` method, and anything that publishes a synthesised signature. The signature object is the single normalised view across all of them, and `kind` is the field that survives the normalisation. ## Version notes The five kinds are stable and unchanged through Python 3.14. What changed historically is only how `POSITIONAL_ONLY` can arise: before Python 3.8 it appeared essentially only on C-implemented callables, because pure-Python code had no way to declare it; PEP 570 added the `/` marker in 3.8, so a plain `def` can now produce that kind too. Nothing about `Parameter.kind` itself changed in the 3.10 → 3.14 range, which makes it one of the safer things to build tooling on.

  • In what order does iterating signature(f).parameters give you the parameters?
    Declaration order. Because the language constrains how parameters may be declared, the kinds you encounter are non-decreasing along that order: positional-only, then positional-or-keyword, then the starred parameter, then keyword-only, then the double-starred one. That means a single left-to-right loop over the mapping is enough; you never need to sort or group the parameters yourself before reasoning about them.
  • Why can a partially applied callable report different kinds than the function it wraps?
    Because pre-supplying an argument changes what the remaining call can look like. Fixing an ordinary positional-or-keyword parameter by keyword makes every parameter after it unreachable positionally, so inspect reports that parameter as KEYWORD_ONLY with the supplied value as its default, and the following ones as keyword-only too. The signature is recomputed to describe the wrapper's real call surface, not copied from the wrapped function.
  • What happens when you call inspect.signature on a callable it cannot describe?
    It raises ValueError. Some C-implemented builtins carry no introspectable signature — `inspect.signature(max)` fails with 'no signature found for builtin' — and a callable can also raise TypeError if it is not callable at all. Any tool that introspects arbitrary user-supplied callables has to handle that path and fall back to calling blindly or to rejecting the callable, rather than assuming a Signature always exists.

A shipping form where some boxes are filled strictly in printed order, some are labelled and can be filled in any order, and one at the bottom is a blank 'anything else' field. The label on a box does not tell you which sort it is; the form's rules do.

saying these in an interview costs you the question

  • Thinks a parameter's name tells you how it can be passed
  • Tests for no default with `default is None`
  • Confuses VAR_POSITIONAL (one star) with VAR_KEYWORD (two stars)
  • Assumes every parameter is positional-or-keyword
  • Believes inspect.signature succeeds on every callable
  • Reads parameter kinds by parsing source text instead

context