Skip to content

What Do / and * Mean in Python Function Parameters?

1. Purpose

In this post, I will demonstrate what the / and * symbols mean in Python function signatures, and how to correctly call functions that use these special parameter markers.

I first encountered this syntax when reading Python documentation:

example.py
def func(a, b, /, c, d, *, e, f):
pass

My immediate reaction was confusion. What are those / and * symbols doing in the middle of a parameter list?

2. The Problem and Solution

2.1 What Error I Got

I tried calling a function with keyword arguments for what I thought were normal parameters:

mistake.py
def greet(name, /, greeting="Hello"):
print(f"{greeting}, {name}!")
greet(name="Alice") # I thought this would work

Instead of a friendly greeting, I got a TypeError:

Terminal window
TypeError: greet() got some positional-only arguments passed as keyword arguments: 'name'

This error confused me. Python normally allows both positional and keyword arguments, so why was this failing?

2.2 Understanding the Parameter Zones

After digging into the Python documentation, I learned that / and * create distinct “zones” in the parameter list:

Parameter zones diagram
[positional-only] / [positional-or-keyword] * [keyword-only]
a, b c, d e, f

The five parameter types in Python are:

  1. Positional-only (before /): Must be passed by position, cannot use keyword syntax
  2. Positional-or-keyword (between / and *): Can use either style
  3. Keyword-only (after *): Must be passed by keyword, cannot use positional syntax
  4. Var positional (*args): Collects extra positional arguments
  5. Var keyword (**kwargs): Collects extra keyword arguments

2.3 The Correct Usage

Going back to my greet function, the name parameter is before the /, so it’s positional-only:

correct.py
def greet(name, /, greeting="Hello"):
print(f"{greeting}, {name}!")
# Correct ways to call:
greet("Alice") # OK: positional-only by position
greet("Alice", "Hi") # OK: greeting can be positional
greet("Alice", greeting="Hi") # OK: greeting can be keyword
# Wrong way:
greet(name="Alice") # TypeError: positional-only as keyword

2.4 Keyword-Only Parameters

The * symbol works similarly but in reverse. Parameters after * must be keyword-only:

keyword_only.py
def create_user(name, *, age, email):
return {"name": name, "age": age, "email": email}
# Correct:
create_user("Bob", age=30, email="[email protected]")
# Wrong:
create_user("Bob", 30, "[email protected]") # TypeError

The error message I got:

Terminal window
TypeError: create_user() takes 1 positional argument but 3 positional arguments were given

2.5 Full Example with All Zones

Here’s a function demonstrating all parameter zones:

full_zones.py
def complex_func(a, b, /, c, d, *, e, f):
"""
a, b: positional-only (must use position)
c, d: positional-or-keyword (either style)
e, f: keyword-only (must use keyword)
"""
return a + b + c + d + e + f
# Valid calls:
complex_func(1, 2, 3, 4, e=5, f=6) # c,d positional
complex_func(1, 2, c=3, d=4, e=5, f=6) # c,d keyword
# Invalid calls:
complex_func(a=1, b=2, c=3, d=4, e=5, f=6) # TypeError: a,b positional-only
complex_func(1, 2, 3, 4, 5, 6) # TypeError: e,f keyword-only

2.6 Why Python Added This Syntax

This syntax was introduced in Python 3.8 via PEP 570. Before this, Python had no way to enforce positional-only parameters in pure Python code (only built-in functions like len() had this capability).

Why positional-only matters:

When I rename internal parameters, I don’t want to break existing code that might be passing them by keyword:

api_evolution.py
# Version 1
def process_data(data, verbose=False):
pass
# Version 2 - renamed parameter
def process_data(data, show_progress=False):
pass # Anyone calling process_data(data, verbose=True) breaks!
# Version 3 - positional-only prevents this
def process_data(data, /, show_progress=False):
pass # No one could have used verbose=True anyway

Why keyword-only matters:

For functions with many optional parameters, keyword-only ensures callers explicitly name what they’re setting:

clarity.py
# Without keyword-only - dangerous!
def configure(timeout=30, retries=3, debug=False, cache=True):
pass
configure(60) # Is this timeout or retries? Ambiguous!
# With keyword-only - explicit
def configure(*, timeout=30, retries=3, debug=False, cache=True):
pass
configure(timeout=60) # Clear what we're setting

2.7 Common Mistake: Confusing * Separator with *args

I initially confused the * separator with *args:

confusion.py
# These are DIFFERENT:
def func1(*, x, y): # * is a separator, x,y are keyword-only
pass
def func2(*args): # *args collects arbitrary positional args
pass
def func3(*args, x, y): # x,y are keyword-only, args collects extras
pass

The key distinction: When * appears alone without a name following it, it’s a separator marking keyword-only parameters. When it appears as *args, it’s a parameter that collects extra positional arguments.

3. Summary

In this post, I explained the / and * symbols in Python function parameters:

  • / marks positional-only parameters: Arguments before / must be passed by position
  • * marks keyword-only parameters: Arguments after * must be passed by keyword
  • Between / and *: Parameters accept either positional or keyword syntax

This syntax helps create cleaner APIs: positional-only protects internal parameters from keyword-based breakage during refactoring, while keyword-only forces explicit naming for functions with many optional parameters.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments