Function Definition with Position-Only Parameters

by Christoph Schiessl on Python

Another interesting trick you see pretty often in the standard library is functions requiring the caller to provide certain parameters using positional notation. To define functions with this requirement, there is a special syntax — using a / in the parameter list. For instance, the parameter a in the following function must be provided as a positional parameter:

def foo(a, /, b):
    pass

Note that the parameter b is not affected at all by the /. For b, the default behavior is still active, meaning the caller can decide if he wants to provide the parameter using positional notation or keyword notation. If we look at the grammar from the official Language Reference again, we can see this in the line that defines parameter_list:

funcdef                   ::=  [decorators] "def" funcname [type_params] "(" [parameter_list] ")"
                               ["->" expression] ":" suite
decorators                ::=  decorator+
decorator                 ::=  "@" assignment_expression NEWLINE
parameter_list            ::=  defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]]
                                 | parameter_list_no_posonly
parameter_list_no_posonly ::=  defparameter ("," defparameter)* ["," [parameter_list_starargs]]
                               | parameter_list_starargs
parameter_list_starargs   ::=  "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                               | "**" parameter [","]
parameter                 ::=  identifier [":" expression]
defparameter              ::=  parameter ["=" expression]
funcname                  ::=  identifier

The expression defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]] defines a list of one or more position-only parameters, optionally followed by all other parameters. The word "optionally" is important because this tells us that the following is also allowed:

def bar(a, b, /):
    pass

Namely, functions that accept only positional parameters and nothing else are also valid. So much for the theory — let's take this new knowledge for a spin:

Python 3.12.1 (main, Jan  1 2024, 16:22:47) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo(a, /, b):
...     pass
...
>>> foo(1, 2)
>>> foo(1, b=2)
>>> foo(a=1, b=2) # doesn't work, because `a` is position-only!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got some positional-only arguments passed as keyword arguments: 'a'
>>> def bar(a, b, /):
...     pass
...
>>> bar(1, 2)
>>> bar(a=1, b=2) # doesn't work, because `a` and `b` are both position-only!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bar() got some positional-only arguments passed as keyword arguments: 'a, b'

One edge case that I can think of is a / without any parameters to its left:

Python 3.12.1 (main, Jan  1 2024, 16:22:47) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def baz(/, a)
  File "<stdin>", line 1
    def baz(/, a)
            ^
SyntaxError: at least one argument must precede /

It makes intuitive sense that this is not allowed. Think about it. What would the purpose of the / in this function be? It wouldn't accomplish anything.

Interplay with Default Parameters

Of course, it's still possible to provide default values for position-only parameters, but not for all of them. The restriction still applies: All parameters without default values must be listed before parameters with default values. Violating this rule results in an immediate SyntaxError:

Python 3.12.1 (main, Jan  1 2024, 16:22:47) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo(a=1, /, b=2):
...     pass
...
>>> def bar(a=1, /, b):
  File "<stdin>", line 1
    def bar(a=1, /, b):
                    ^
SyntaxError: parameter without a default follows parameter with a default

That's everything for today. Thank you for reading, and see you soon!

Ready to Learn More Web Development?

Join my Mailing List to receive two articles per week.


I send two weekly emails on building performant and resilient Web Applications with Python, JavaScript and PostgreSQL. No spam. Unscubscribe at any time.

Continue Reading?

Here are a few more Articles for you ...


Function Definition with Keyword-Only Parameters

Learn about keyword-only parameters in Python, how to define them, and their interplay with position-only parameters.

By Christoph Schiessl on Python

Function Definition with Catch-All Parameters

Learn how to use catch-all parameters in Python functions with this guide. Capture excess positional and keyword arguments to make your functions more flexible.

By Christoph Schiessl on Python

Encapsulation: Public, Protected, and Private Members

Learn about encapsulation in Python. This article explains public, protected, and private members and the conventions used to implement them.

By Christoph Schiessl on Python

Christoph Schiessl

Hi, I'm Christoph Schiessl.

I help you build robust and fast web applications.


I'm available for hire as a freelance web developer, so you can take advantage of the more than a decade of experience I have collected working on many projects across several industries. Most of my clients are building web-based SaaS applications in a B2B context and depend on my expertise in various capacities.

More often than not, my involvement includes hands-on development work using technologies like Python, JavaScript, and PostgreSQL. Furthermore, if you already have an established team, I can support you as a technical product manager with a passion for simplifying complex processes. Lastly, I'm also a skilled writer who takes pride in breaking down technical concepts into the simplest possible terms.