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

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.