Positional-Only Parameters in Python — Core Concepts

Why positional-only parameters matter

Python 3.8 introduced PEP 570, which lets you mark function parameters as positional-only using a / separator. This formalized something built-in functions had always done — len(obj) never accepted len(obj=mylist) — but now you can do it in your own code.

This matters for API design. When you publish a library, parameter names become part of your public interface. If someone writes my_func(name="Alice"), you can never rename name to label without a breaking change. Positional-only parameters free you from that commitment.

The syntax

def greet(name, /, greeting="Hello", *, loudly=False):
    msg = f"{greeting}, {name}!"
    return msg.upper() if loudly else msg

Three zones:

  • Before / → positional-only (name)
  • Between / and * → normal, either positional or keyword (greeting)
  • After * → keyword-only (loudly)

Valid calls:

greet("Alice")                          # ✅
greet("Alice", "Hi")                    # ✅
greet("Alice", greeting="Hi")           # ✅
greet("Alice", loudly=True)             # ✅
greet(name="Alice")                     # ❌ TypeError
greet("Alice", "Hi", True)              # ❌ TypeError (loudly is keyword-only)

Three reasons to use positional-only parameters

1. Freedom to rename internal parameters

Library authors can rename parameters without breaking callers:

# Version 1
def connect(host, /, port=5432):
    ...

# Version 2 — renamed internally, no breaking change
def connect(hostname, /, port=5432):
    ...

Since callers must write connect("localhost") rather than connect(host="localhost"), the rename is invisible.

2. Allowing parameter names as keyword arguments

A powerful pattern for functions that accept arbitrary keyword arguments:

def make_tag(name, /, **attrs):
    attr_str = " ".join(f'{k}="{v}"' for k, v in attrs.items())
    return f"<{name} {attr_str}>"

make_tag("div", id="main", name="container")
# Output: <div id="main" name="container">

Without /, using name="container" would conflict with the name parameter. The positional-only marker makes name refer to the first argument, while **attrs captures everything else.

3. Cleaner mathematical and utility functions

For functions where parameter names are meaningless:

def distance(x1, y1, x2, y2, /):
    return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5

Nobody should write distance(x1=0, y1=0, x2=3, y2=4). The positional form distance(0, 0, 3, 4) is clearer and more natural.

How the standard library uses it

Many built-in functions are positional-only:

FunctionSignatureWhy
len(obj, /)Object name is irrelevant
range(stop, /)Short utility, names add nothing
int(x, /, base=10)x is implementation detail
sorted(iterable, /, *, key, reverse)iterable is positional, options are keyword

The sorted pattern — positional for the data, keyword-only for configuration — is a common and recommended API design.

Common misconception

“Positional-only is about limiting what callers can do.”

It’s actually about protecting callers. When a library makes a parameter positional-only, it promises: “We might change this name, but your code will keep working.” It’s a compatibility guarantee, not a restriction.

Combining all three zones

The full parameter model in Python now has three clear zones:

def example(pos_only, /, normal, *, kw_only):
    ...

This gives you precise control over your function’s calling convention. Use positional-only for implementation details, normal for flexible parameters, and keyword-only for configuration flags.

When to use positional-only

  • Library public APIs where you want naming freedom
  • Functions accepting **kwargs where parameter names might conflict
  • Mathematical functions where positional semantics are more natural
  • Wrapper functions that pass arguments through to other callables

When not to use it: internal functions where naming freedom isn’t needed, or when keyword arguments genuinely improve readability at the call site.

The one thing to remember: The / marker says “these names are mine, not part of the public contract” — giving library authors room to evolve without breaking callers.

pythonfundamentalspython38

See Also

  • Python 310 New Features Python 3.10 gave programmers a shape-sorting machine, friendlier error messages, and cleaner ways to say 'this or that' in type hints.
  • Python 311 New Features Python 3.11 made everything faster, error messages smarter, and let you catch several mistakes at once instead of stopping at the first one.
  • Python 312 New Features Python 3.12 made type hints shorter, f-strings more powerful, and started preparing Python's engine for a world without the GIL.
  • Python 313 New Features Python 3.13 finally lets multiple tasks run at the same time for real, added a speed booster engine, and gave the interactive prompt a colourful makeover.
  • Python Exception Groups Python's ExceptionGroup is like getting one report card that lists every mistake at once instead of stopping at the first one.