Parameter Mechanisms¶
All four of these calls produce the same output, but each uses a different passing mechanism:
def describe(name: str, age: int) -> None:
print(name, age)
describe("Alice", 25) # positional
describe(age=25, name="Alice") # keyword
describe(*("Alice", 25)) # unpacking tuple
describe(**{"age": 25, "name": "Alice"}) # unpacking dict
This page covers every mechanism Python offers for getting arguments into a function.
Parameter Categories¶
Python has five categories of parameters:
- Positional-or-keyword - Can be passed either way
- Positional-only - Must be passed by position (before
/) - Keyword-only - Must be passed by name (after
*) *args- Captures extra positional arguments**kwargs- Captures extra keyword arguments
Basic Positional and Keyword Arguments¶
By default, parameters can be passed positionally or by keyword.
def greet(name: str, greeting: str) -> str:
return f"{greeting}, {name}!"
# All equivalent
greet("Alice", "Hello") # Positional
greet(name="Alice", greeting="Hello") # Keyword
greet("Alice", greeting="Hello") # Mixed
A positional argument followed by a keyword argument is fine, but the reverse is a SyntaxError:
greet("Alice", greeting="Hello") # OK
greet(name="Alice", "Hello") # SyntaxError
Default Values¶
Parameters can have default values, making them optional.
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
greet("Alice") # "Hello, Alice!"
greet("Alice", "Hi") # "Hi, Alice!"
Rule: Parameters with defaults must come after parameters without defaults.
# Valid
def func(a: int, b: int, c: int = 10, d: int = 20) -> None:
pass
# Invalid - SyntaxError
def func(a: int, b: int = 10, c: int) -> None: # Non-default after default
pass
*args: Variable Positional Arguments¶
*args captures any extra positional arguments as a tuple.
def sum_all(*numbers: int) -> int:
return sum(numbers)
sum_all(1, 2) # 3
sum_all(1, 2, 3, 4, 5) # 15
sum_all() # 0
Inside the function, args is an ordinary tuple:
def show_args(*args: int) -> None:
print(type(args)) # <class 'tuple'>
print(args)
show_args(1, 2, 3) # (1, 2, 3)
**kwargs: Variable Keyword Arguments¶
**kwargs captures any extra keyword arguments as a dictionary.
def print_info(**kwargs: str) -> None:
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age="30", city="NYC")
Output:
name: Alice
age: 30
city: NYC
Combining args and *kwargs¶
A function can accept any combination of positional and keyword arguments with *args and **kwargs together. This is useful when forwarding arguments to another function:
def log_and_call(func, *args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
return func(*args, **kwargs)
def add(a: int, b: int) -> int:
return a + b
log_and_call(add, 1, 2) # Calling add with args=(1, 2), kwargs={}
log_and_call(add, a=1, b=2) # Calling add with args=(), kwargs={'a': 1, 'b': 2}
Unpacking Arguments¶
Use * and ** when calling functions to unpack iterables and dictionaries.
def add(a: int, b: int, c: int) -> int:
return a + b + c
# Unpack list/tuple
numbers = [1, 2, 3]
add(*numbers) # 6
# Unpack dictionary
params = {"a": 1, "b": 2, "c": 3}
add(**params) # 6
# Combined
add(*[1, 2], **{"c": 3}) # 6
Positional-Only Parameters (/)¶
Parameters before / must be passed positionally. Positional-only parameters are rare in everyday code but appear in many built-in functions like len(). This syntax requires Python 3.8 or later.
def divide(x: float, y: float, /) -> float:
return x / y
divide(10, 2) # OK: 5.0
divide(x=10, y=2) # TypeError: positional-only argument
# Built-in example: len() is positional-only
len([1, 2, 3]) # OK
len(obj=[1, 2, 3]) # TypeError
Keyword-Only Parameters (*)¶
Parameters after * must be passed by keyword. This forces callers to be explicit, which prevents confusing positional calls.
def connect(host: str, port: int, *, timeout: int = 30, retries: int = 3) -> None:
print(f"Connecting to {host}:{port}")
print(f"timeout={timeout}, retries={retries}")
connect("localhost", 8080) # OK
connect("localhost", 8080, timeout=60) # OK
connect("localhost", 8080, 60) # TypeError
Without keyword-only parameters, boolean flags become unreadable:
# What does True mean? What does False mean?
process_file("data.txt", True, False)
# Keyword-only makes the intent clear
def process_file(path: str, *, verbose: bool = False, overwrite: bool = False) -> None:
pass
process_file("data.txt", verbose=True, overwrite=False)
Combined Syntax¶
You can combine / and * in the same function now that both separators are understood:
def func(pos_only: int, /, standard: int, *, kw_only: int) -> None:
print(f"{pos_only}, {standard}, {kw_only}")
func(1, 2, kw_only=3) # OK
func(1, standard=2, kw_only=3) # OK
func(pos_only=1, standard=2, kw_only=3) # TypeError
func(1, 2, 3) # TypeError
Real-World Examples¶
print() signature¶
# Simplified signature of print()
def print(*values, sep=' ', end='\n', file=None):
pass
print(1, 2, 3) # 1 2 3
print(1, 2, 3, sep='-') # 1-2-3
print(1, 2, 3, end='!\n') # 1 2 3!
sorted() signature¶
# sorted(iterable, /, *, key=None, reverse=False)
sorted([3, 1, 2]) # [1, 2, 3]
sorted([3, 1, 2], reverse=True) # [3, 2, 1]
sorted(["banana", "Apple"], key=str.lower) # ['Apple', 'banana']
The key function transforms each element for comparison only — the original values appear in the output. Here str.lower makes the sort case-insensitive, but 'Apple' keeps its capital A in the result.
Quick Reference¶
| Syntax | Meaning |
|---|---|
def f(a, b) |
Standard parameters |
def f(a, b=10) |
Default value |
def f(a, /) |
Positional-only (before /) |
def f(*, a) |
Keyword-only (after *) |
def f(*args) |
Variable positional (tuple) |
def f(**kwargs) |
Variable keyword (dict) |
def f(a, /, b, *args, c, **kwargs) |
Full parameter order |
f(*list) |
Unpack iterable at call site |
f(**dict) |
Unpack dictionary at call site |