Marking End of Positional Parameters in Python
A bare * in a Python function signature marks the end of positional parameters. Everything after it is keyword-only: callers must pass it by name.
def exec(self, command: str, *, workdir: str | None = None, tty: bool = False):
...
exec("ls", workdir="/tmp", tty=True) # OK
exec("ls", "/tmp") # TypeError
The Aha!: the * doesn’t change how valid calls execute. It changes which calls are valid. It’s a constraint on the caller, not on the runtime.
Where is it helpful?
- Signatures are contracts as much as mechanics. The
*costs nothing at runtime but encodes intent: “these are tuning knobs, name them.” - It buys the API author freedom. Keyword-only params can be reordered or added anywhere without breaking callers, which matters most in base classes and interfaces shared by several implementations.
- It kills a class of silent bugs:
exec("ls", None, 10.0, True)is unreadable and easy to get wrong. Bare booleans and numbers at call sites carry no meaning. - It also shows up in introspection (
inspect.signature()reportsKEYWORD_ONLY), which matters for tools that build CLIs or schemas from function signatures.