Back to Blog
Python

Python Keyword-Only Star Syntax Explained

Learn how the bare * in Python function signatures enforces keyword-only arguments, with examples and common pitfalls.

keyword-only argumentsfunction signaturespython syntaxparameter handlingpython functions
A clean illustration of a Python function signature with a bare asterisk separating positional and keyword-only parameters.

The bare * in a Python function definition marks all following parameters as keyword-only. That means callers must pass those arguments by name, not by position. It is a small syntax feature, but it can make an API clearer and prevent call-site mistakes.

What the Bare * Does in a Function Signature

In a Python function signature, * appears in two distinct ways. *args collects extra positional arguments into a tuple. A bare * collects nothing. Instead, it acts as a separator: every parameter declared after it becomes keyword-only.

Consider this function:

def configure(host, port, *, timeout, retries): pass

Here, host and port are positional-or-keyword parameters, so callers can pass them either by position or by name. timeout and retries are keyword-only. The bare * tells Python that no further positional arguments are allowed after that point.

This is enforced at call time. If you call configure("localhost", 8080, 5, 3), Python raises a TypeError because the function accepts only two positional arguments. The only way to pass timeout and retries is by using their names.

Minimal Example: Enforcing Keyword-Only Parameters

The most common use of the bare * is to force callers to be explicit about arguments whose meaning is not obvious from position alone.

def send_message(recipient, *, subject=None, priority=1): print(f"To: {recipient}") if subject: print(f"Subject: {subject}") print(f"Priority: {priority}")

Calling send_message("alice@example.com", "Hello", 2) fails because subject and priority are keyword-only. The correct call is:

send_message("alice@example.com", subject="Hello", priority=2)

This prevents a caller from accidentally swapping subject and priority, and it makes the call site self-documenting.

Mixing Positional, Keyword-Only, and Variable-Length Parameters

The bare * can appear alongside *args and the positional-only marker /. The general order is:

  1. Positional-only parameters (before /)
  2. Positional-or-keyword parameters (between / and * or *args)
  3. *args or bare *
  4. Keyword-only parameters
  5. **kwargs

Here is an example that combines all of them:

def process(data, /, mode, *, verbose=False, **options): pass

data is positional-only, mode is positional-or-keyword, and verbose is keyword-only. **options collects any additional keyword arguments. The / marker is available from Python 3.8 onward; the bare * itself is available in all Python 3 versions.

When you use *args instead of a bare *, the parameters after *args are also keyword-only. The difference is that *args also captures extra positional arguments. If you do not need to capture extra positional arguments, use the bare *; it signals that the function has a fixed number of positional parameters and avoids creating the tuple that *args would collect.

Common Mistakes and Misconceptions

A common mistake is writing def f(*args, a): when you meant def f(*, a):. The first form is valid, but it collects extra positional arguments into args; a is still keyword-only because it follows *args, but the signature is not the one you intended. The bare-star form requires a comma between * and the first keyword-only parameter.

Another misconception is that the bare * itself can be used as a parameter name. It cannot. It is purely a marker and has no value inside the function body.

Some developers confuse the bare * in a definition with the unpacking operator used in function calls. In a call, *iterable unpacks an iterable into positional arguments. In a definition, * marks the boundary for keyword-only parameters. These are distinct contexts and should not be mixed.

Another invalid arrangement is placing the bare * after **kwargs. **kwargs must be the last parameter, so the correct order always puts it at the end.

When to Use Keyword-Only Arguments

Keyword-only arguments are most valuable when a function has several optional parameters or parameters with similar types. For example, a function that creates a user might have name, email, age, and location. Requiring age and location to be keyword-only prevents calls like create_user("Alice", "alice@example.com", 30, "NYC"), where the meaning of 30 and "NYC" is ambiguous.

They are also useful in APIs that evolve. If you add a new optional parameter after a bare *, existing positional calls do not break because the new parameter cannot be passed positionally. Existing callers continue to work, and new callers must use the keyword form, which is clearer.

Keyword-only arguments are not always appropriate. If a function has a natural positional order and every parameter is required, forcing keyword-only usage adds verbosity without much benefit. Use the bare * when clarity or safety outweighs the extra typing.

Compatibility and Maintainability Considerations

The bare * was introduced in Python 3.0, so any codebase running Python 3 can use it. If you maintain a library that supports Python 2, you cannot use this syntax, but Python 2 reached end-of-life in 2020, so this is rarely a concern today.

From a maintainability perspective, keyword-only arguments make call sites more readable and reduce the chance of errors when parameters are reordered. They also make it easier to add new parameters later without breaking existing callers, as long as the new parameters are placed after the bare *.

One tradeoff is that keyword-only arguments cannot be passed positionally, which can be inconvenient for functions that are called frequently with many arguments. In such cases, the extra verbosity at the call site may outweigh the safety benefit. Consider how the function will be used before deciding.

Runtime Behavior and Performance Notes

Enforcement of keyword-only arguments happens when a call is bound. If too many positional arguments are supplied, Python raises a TypeError before the function body executes. For ordinary code, this is not a performance concern.

Using *args instead of a bare * does have a small runtime cost because Python must create a tuple for the collected positional arguments. If you do not need variable-length positional arguments, using the bare * avoids that allocation. The difference is negligible in most code, but it is still a reason to prefer the bare * when you do not need *args.

The bare * also interacts with introspection tools. Tools like inspect.signature correctly report keyword-only parameters, which helps documentation generators and IDEs show accurate signatures.

Advanced Usage: Forcing Keyword-Only in Class Constructors

The bare * is especially useful in __init__ methods when you want to prevent callers from relying on positional argument order. For example:

class DatabaseConnection: def __init__(self, host, port, *, user, password): self.host = host self.port = port self.user = user self.password = password

This forces user and password to be passed by name, which is safer because they are sensitive and easy to confuse. It also makes the constructor call more explicit:

conn = DatabaseConnection("localhost", 5432, user="admin", password="secret")

If you later add an optional ssl parameter, you can add it after the * without breaking existing calls that pass user and password as keyword arguments.

Parameter Kind Summary

Parameter kindMarkerPassed by position?Passed by keyword?
Positional-only/YesNo
Positional-or-keyword(none)YesYes
Keyword-onlybare *NoYes
Var-positional*argsExtra positional arguments are collectedNo
Var-keyword**kwargsNoExtra keyword arguments are collected

This table summarizes the five parameter kinds. The bare * is the marker for keyword-only parameters. It is a deliberate design choice that improves API clarity and reduces the chance of misuse.

Python Keyword-Only Star Syntax: Usage and Common Pitfalls | RYUSLOG DEV