Function Definition with Keyword-Only Parameters

by Christoph Schiessl on Python

The opposite of position-only parameters is keyword-only parameters, and they are equally or maybe even more common in the Python world. As you may have already guessed, functions can define specific parameters as keyword-only and thereby require the caller of those functions to provide these parameters using keyword notation. There is a special syntax for this, using the * character:

def foo(a, *, b):
    pass

The * has the following effect: All parameters to the right of the * must be provided using keyword notation — in this case, the only such parameter is b. Parameters to the left of the * are not affected, meaning their default behavior is still active. As a quick reminder: Default behavior means the caller can decide if he prefers positional or keyword notation.

Similarly, we can define functions that accept only keyword parameters:

def bar(* a, b):
    pass

The function above has two parameters, a and b, both of which must be provided using keyword notation. For the sake of completeness, here is the grammar from the official Language Reference:

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

What we are talking about is the line that defines parameter_list_starargs. The grammar tells us about the allowed syntax, but it doesn't explain the semantics, meaning it tells us where a * can occur in the parameter list, but it doesn't explain how this affects the function's caller. In any case, the line defining parameter_list_no_posonly references parameter_list_starargs, which means that the * syntax can be used in a longer list of parameters as we already demonstrated when we defined foo(a, *, b).

So far, so good. Let's see it in action now ...

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, b=2)   # `b` must be supplied as a keyword parameter
>>> foo(a=1, b=2) # `a` can be supplied as a positional and a keyword parameter
>>> foo(1, 2)     # TypeError if you try passing `b` as a positional parameter
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes 1 positional argument but 2 were given
>>> def bar(*, a, b):
...     pass
...
>>> bar(a=1, b=2) # `a` and `b` must be supplied as keyword parameters
>>> bar(1, b=2)   # TypeError if you try passing `a` as a positional parameter
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bar() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
>>> bar(1, 2)     # TypeError if you try passing `a` and `b` as positional parameters
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bar() takes 0 positional arguments but 2 were given

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

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, *) # SyntaxError: At least one parameter must be to the right of the `*`.
  File "<stdin>", line 1
    def baz(a, *)
               ^
SyntaxError: named arguments must follow bare *

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 Position-Only Parameters

You can, of course, combine keyword-only and position-only parameters, as long as you keep certain things in mind:

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, *, c):
...     pass
...
>>> foo(1, 2, 3)       # TypeErorr because `c` comes after the `*`, and therefore, it's keyword-only
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes 2 positional arguments but 3 were given
>>> foo(1, 2, c=3)
>>> foo(1, b=2, c=3)
>>> foo(a=1, b=2, c=3) # TypeError: `a` comes before the `/`, and therefore, it's  positon-only
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got some positional-only arguments passed as keyword arguments: 'a'

What's not allowed is to put a * before the /. But, it's valid to put a * immediately after a / to define a function that leaves no flexibility to the caller.

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 bar(a, /, *, b):
...     pass
...
>>> bar(1, b=2)
>>> bar(1, 2)     # TypeErorr because `b` comes after the `*`, and therefore, it's keyword-only
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bar() takes 1 positional argument but 2 were given
>>> bar(a=1, b=2) # TypeError: `a` comes before the `/`, and therefore, it's  positon-only
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bar() got some positional-only arguments passed as keyword arguments: 'a'

Thank you very much for reading, and see you soon! Please don't hesitate to reach out if you have any questions.

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 Position-Only Parameters

Learn about positional parameters in Python and a special syntax that allows functions to declare certain parameters as position-only.

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

The Built-in min() and max() Functions

Python's min() and max() functions can take an iterable or multiple positional parameters, and support a key parameter for custom comparisons.

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.