What's the difference between a positional-only, keyword-only, and regular parameter?
Quick Answer
A regular parameter can be passed either positionally or by keyword. Parameters after a bare `*` in the signature are **keyword-only** — they must be passed by name. Parameters before a bare `/` (Python 3.8+) are **positional-only** — they cannot be passed by name. These markers let API authors control call-site flexibility: positional-only protects against callers depending on a parameter name that might change; keyword-only forces clarity for ambiguous or rarely-used options.
Detailed Answer
The three parameter kinds
def f(pos_only, /, regular, *, kw_only):
...
f(1, 2, kw_only=3) # OK
f(1, regular=2, kw_only=3) # OK -- `regular` can be positional or keyword
f(pos_only=1, regular=2, kw_only=3) # TypeError -- pos_only can't be a keyword
f(1, 2, 3) # TypeError -- kw_only must be named
- Everything before
/is positional-only: callers cannot use the parameter name at the call site at all. - Everything after
*is keyword-only: callers must use the parameter name; passing it positionally is an error. - Everything between
/and*(or with no markers used) is regular — callable either way.
Why keyword-only matters: clarity for ambiguous calls
def create_user(name, *, is_admin=False, send_email=True):
...
create_user("ada", True, False) # not allowed -- what do True/False mean?!
create_user("ada", is_admin=True, send_email=False) # forced to be explicit
Forcing is_admin/send_email to be keyword-only prevents a call site
like create_user("ada", True, False), where a reader has no idea which
boolean means what without checking the signature.
Why positional-only matters: API stability
def len(obj, /):
...
Many built-ins (len, abs, pow) are positional-only in their actual
C implementation, and Python surfaces that with /. Marking a parameter
positional-only means you can later rename it in a new library
version without breaking any caller — since no caller was allowed to
depend on the name in the first place. This matters most for public
library APIs where the parameter name isn't a meaningful part of the
contract but the position is.
Combining both
def divide(a, b, /, *, rounding=None):
...
divide(10, 3) # OK
divide(10, 3, rounding="up") # OK
divide(a=10, b=3) # TypeError -- a, b are positional-only
Interview-ready summary: / marks everything before it as
positional-only (protects the parameter name from becoming part of the
public contract); * marks everything after it as keyword-only (forces
callers to be explicit). Regular parameters, with neither marker, accept
both call styles — the default and most common case.