Keyword-only and positional-only parameters
Overview
Python lets you enforce HOW arguments must be passed using two special markers in the signature. The `*` (bare star) makes everything after it keyword-only, meaning callers MUST name those arguments. It acts as a "name required from here on" wall. def create_user(name, *, role='member', active=True): pass create_user('alice', role='admin') # ok create_user('alice', 'admin') # TypeError The `/` (forward slash) makes everything before it positional-only, meaning callers CANNOT name those arguments. It acts as a "no naming allowed before this" wall. def build_path(base, endpoint, /): return base + endpoint build_path('/api', '/users') # ok build_path(base='/api', endpoint='/users') # TypeError Both markers can appear together in the same function signature. The `/` marker requires Python 3.8+. def f(pos_only, /, normal, *, kw_only): pass def delete_record(record_id, *, confirm): pass delete_record(42) # TypeError: missing 1 required keyword-only argument: 'confirm' Keyword-only parameters without default values become strictly required. Callers are forced to explicitly name them, which is perfect for dangerous operations or boolean flags. def invalid_order(a, *, b, c, /): pass # SyntaxError: positional-only argument follows keyword-only argument The parameter order is strictly enforced by Python. Positional-only parameters (and `/`) must appear before regular parameters, which must appear before the keyword-only marker (`*`).
Keyword-only parameters make call sites self-documenting (e.g., `create_user('alice', role='admin')` is clearer than `create_user('alice', 'admin')`) and prevent callers from passing booleans in the wrong order. Positional-only parameters let library authors rename internal parameters without breaking callers.
Where used: FastAPI `Depends()` uses keyword-only injection, Python stdlib (`sorted(key=..., reverse=...)`), Pydantic model constructors
Why learn this
- Writing functions where boolean flags must be named (e.g., `send_email(..., *, dry_run=False)`) to prevent silent argument-order bugs
- Reading Python 3.8+ library APIs that use `/` in their signatures
Code walkthrough
def resize(width, height, /, *, quality='high'):
print(f'{width}x{height} at {quality}')
resize(1920, 1080)
resize(1280, 720, quality='low')
Focus: `width` and `height` are positional-only (before `/`); `quality` is keyword-only (after `*`) — the call site must name `quality` but cannot name `width` or `height`.
Common mistakes
- Passing a keyword-only argument positionally: If a parameter follows `*` in the signature, you must name it. This means you must pass it as a keyword argument (where the parameter name is the keyword). Passing it as a positional argument raises `TypeError`.
- Naming a positional-only argument: If a parameter precedes `/` in the signature, you cannot pass it by name. Doing so raises `TypeError`.
- Confusing `*` (keyword-only marker) with `*args`: A bare `*` in the parameter list is just a separator — it collects nothing. `*args` is a named collector for extra positional arguments. They look similar but behave completely differently.
- Forgetting that keyword-only parameters without defaults are required: If a keyword-only parameter lacks a default value, Python makes it a required keyword-only argument. Calling the function without it raises a `TypeError`.
Glossary
- signature
- The definition of a function that shows its name and exactly what arguments it expects to receive. Example: `def add(a, b):`.
- call sites
- The specific places in your code where a function is actually executed or invoked. Example: `result = my_func(5)` is a call site.
- keyword argument
- Passing a value to a function by explicitly writing the parameter's name (the keyword), followed by an equals sign, and then the value. Example: `role='admin'`.
- positional argument
- Passing a value to a function purely based on its order in the list, without writing any parameter name. Example: in `create_user('alice')`, `'alice'` is a positional argument because it's first.
- keyword-only
- A parameter that MUST be passed as a keyword argument, meaning you are forced to type out its name. Example: `def f(*, kw_only=True):`.
- positional-only
- A parameter that MUST be passed as a positional argument, meaning you are forbidden from typing out its name. Example: `def f(pos_only, /):`.
- TypeError
- An error that Python throws when an operation or function is applied to an object of inappropriate type, or when function arguments don't match the signature. Example: `len(5)` raises a `TypeError`.
Recall questions
- What does a bare `*` in a function signature enforce?
- What does `/` in a function signature enforce?
- What practical benefit does using `*` to require keyword arguments provide?
Understanding checks
What is the output of this code?
localhost:8080 (False)
`host` is positional-only (passed as "localhost"), `port` can be passed normally (passed as a keyword argument here), and `secure` has a default value, so it doesn't need to be provided.
Why does this function call raise a `TypeError`?
Because `url` is a positional-only parameter, but it is being passed as a keyword argument.
The `/` in the signature enforces that all parameters before it (like `url`) must be passed purely by their position, not by name.
Practice tasks
Refactor to Enforce Keyword and Positional Arguments
The function below currently allows any argument to be passed positionally or by name. Modify the function signature so that `user_id` and `message` MUST be passed positionally, and `dry_run` and `urgent` MUST be passed by keyword.
Challenge
Safe cache setter
Write `set_cache(key, value, /, *, ttl=60, overwrite=False)` where `key` and `value` are positional-only (internal names may change) and `ttl` and `overwrite` are keyword-only flags. Print a summary line for each call. Demonstrate three calls: default, custom `ttl`, and `overwrite=True`.
Continue learning
Previous: Defining functions and return values
Previous: *args and **kwargs