What's the difference between a positional-only, keyword-only, and regular parameter?

5 minintermediatefunctionsparameterspython3.8

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.