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:
| Function | Signature | Why |
|---|---|---|
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
**kwargswhere 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.
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.