Python Keyword-Only Arguments Using Asterisk

This article explains how Python uses the asterisk (*) symbol in function definitions to enforce keyword-only arguments. You will learn the syntax for implementing a bare asterisk versus *args, how Python's runtime enforces these constraints by raising specific TypeError exceptions, and the practical software design benefits of requiring explicit parameter names during function calls.


The Role of the Asterisk (*)

In Python, any parameter defined after an asterisk in a function signature cannot be supplied as a positional argument. The asterisk acts as a boundary marker that consumes positional arguments, requiring callers to explicitly name any following parameters.

There are two ways to define keyword-only arguments:

  1. A bare asterisk (*): Used when the function does not accept arbitrary positional arguments.
  2. A variable positional parameter (*args): Used when the function accepts arbitrary positional arguments, with subsequent parameters strictly bound to keyword calls.

Syntax and Implementation

1. Using a Bare Asterisk

A standalone * does not create a parameter itself; instead, it signals the end of positional arguments.

def configure_server(host, port, *, secure=False, timeout=30):
    return f"Connecting to {host}:{port} (Secure: {secure}, Timeout: {timeout})"

In this signature:

# Valid calls:
configure_server("localhost", 8080)
configure_server("localhost", 8080, secure=True)

# Invalid call:
configure_server("localhost", 8080, True, 60)

2. Using *args

If a function takes an arbitrary number of positional arguments via *args, any parameter placed after *args automatically becomes keyword-only.

def calculate_sum(*values, round_output=False):
    total = sum(values)
    return round(total) if round_output else total

# Valid calls:
calculate_sum(1.2, 2.5, 3.1)
calculate_sum(1.2, 2.5, 3.1, round_output=True)

# Invalid call:
calculate_sum(1.2, 2.5, 3.1, True)

In the invalid call, True is treated as another value collected by *values, rather than being assigned to round_output.


How Python Enforces Keyword-Only Constraints

Python enforces keyword-only rules at runtime during parameter binding. When a function is invoked, the Python interpreter maps provided values to parameters in three steps:

  1. It binds provided positional arguments to parameters appearing before the * or *args.
  2. Any excess positional arguments are either absorbed by *args or trigger an error if a bare * was used.
  3. It binds keyword arguments to matching parameter names.

If you attempt to pass a keyword-only argument positionally when using a bare *, Python raises a TypeError:

configure_server("localhost", 8080, True)
# TypeError: configure_server() takes 2 positional arguments but 3 were given

If a keyword-only argument does not have a default value and is omitted from the call, Python raises a missing argument error:

def create_user(username, *, email):
    pass

create_user("johndoe")
# TypeError: create_user() missing 1 required keyword-only argument: 'email'

Why Use Keyword-Only Arguments?