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
/ marks everything before it as
positional-only, protecting the parameter name from becoming part of the
public contract. * marks everything after it as keyword-only, forcing
callers to be explicit. Regular parameters, with neither marker, accept
both call styles — the default and most common case.