Function Arguments
Python gives you several ways to pass values into a function: by position, by keyword, or a mix of both. Since Python 3.8, you can even force certain parameters to be positional-only or keyword-only. Understanding these rules helps you write function signatures that are both flexible and hard to misuse.
Positional Arguments
Positional arguments are matched to parameters purely by their order — the first argument fills the first parameter, the second fills the second, and so on.
def describe_pet(name, animal_type):
print(f"{name} is a {animal_type}.")
describe_pet("Rex", "dog")
describe_pet("dog", "Rex") # order matters! swapped meaningRex is a dog. dog is a Rex.
Keyword Arguments
Keyword arguments are passed as name=value, matching the parameter by name instead of position. This makes the call self-documenting and lets you reorder arguments freely.
def describe_pet(name, animal_type):
print(f"{name} is a {animal_type}.")
describe_pet(animal_type="dog", name="Rex")
describe_pet(name="Milo", animal_type="cat")Rex is a dog. Milo is a cat.
Mixing Positional and Keyword Arguments
You can combine both styles in a single call, but positional arguments must always come before keyword arguments.
def describe_pet(name, animal_type, age=1):
print(f"{name} is a {age}-year-old {animal_type}.")
describe_pet("Rex", animal_type="dog", age=3) # OK
describe_pet("Rex", age=3, animal_type="dog") # OK, keyword order flexibleRex is a 3-year-old dog. Rex is a 3-year-old dog.
Positional-Only Parameters (`/`)
Since Python 3.8, a / in the parameter list marks everything before it as positional-only — those parameters can never be passed by keyword. This is useful when a parameter name is an implementation detail that shouldn't be part of the public API.
def power(base, exponent, /):
return base ** exponent
print(power(2, 10)) # OK — positional
print(power(base=2, exponent=10)) # TypeError: positional-only1024 TypeError: power() got some positional-only arguments passed as keyword arguments: 'base, exponent'
Keyword-Only Parameters (`*`)
A * in the parameter list marks everything after it as keyword-only — those parameters must be passed by name. This forces callers to be explicit about what a value means, which is especially valuable for boolean flags or rarely-used options.
def create_user(username, *, is_admin=False):
role = "admin" if is_admin else "member"
print(f"{username} created as {role}")
create_user("ada", is_admin=True) # OK
create_user("ada", True) # TypeError: too many positional argumentsada created as admin TypeError: create_user() takes 1 positional argument but 2 were given
Combining `/` and `*`
A single signature can use both markers to define three zones: positional-only, normal (either style), and keyword-only.
def transfer(amount, /, account, *, note=""):
print(f"Transferring {amount} to {account}. Note: {note or '(none)'}")
transfer(100, "checking", note="rent")
transfer(100, account="savings")Transferring 100 to checking. Note: rent Transferring 100 to savings. Note: (none)
Argument Order Rules
Zone | Syntax marker | How it can be passed |
|---|---|---|
Positional-only | before | By position only |
Positional-or-keyword | between | By position or keyword |
Keyword-only | after | By keyword only |
| collects extra positional args | Not addressable by name |
| collects extra keyword args | Addressable only by name, dynamically |
In a function definition, the order must be: positional-only, then positional-or-keyword, then
*args, then keyword-only, then**kwargs.In a function call, all positional arguments must come before any keyword arguments.
def charge(amount, /, *, currency="USD"):
print(f"Charging {amount} {currency}")
charge(49.99)
charge(49.99, currency="EUR")Charging 49.99 USD Charging 49.99 EUR