Function Arguments
In Functions you passed values to a function in a fixed order. Python gives you much more control than that. You can name arguments when you call, give parameters default values, accept any number of values, and force callers to pass some values by name.
These features make functions easier to call correctly. They also explain code you will read in real projects, where signatures such as def run(*args, **kwargs) appear everywhere.
Positional and keyword arguments
A positional argument is matched to a parameter by its position: the first value goes to the first parameter, and so on. A keyword argument is matched by name, written as name=value. With keyword arguments the order no longer matters, and the call explains itself.
def book_ticket(passenger, source, destination):
print(f"{passenger}: {source} to {destination}")
book_ticket("Ananya", "Pune", "Jaipur")
book_ticket(destination="Kochi", passenger="Rohan", source="Lucknow")
book_ticket("Isha", destination="Rohtak", source="Delhi")Ananya: Pune to Jaipur Rohan: Lucknow to Kochi Isha: Delhi to Rohtak
You can mix both kinds, but positional arguments must come first. Writing book_ticket(passenger="Isha", "Delhi", "Rohtak") is a SyntaxError: positional argument follows keyword argument.
Default parameter values
A parameter can have a default value, written as name=value in the definition. If the caller leaves that argument out, the default is used.
def chai_order(cups, sugar=True, size="regular"):
sweet = "with sugar" if sugar else "no sugar"
return f"{cups} {size} chai, {sweet}"
print(chai_order(2))
print(chai_order(1, sugar=False))
print(chai_order(3, size="large"))2 regular chai, with sugar 1 regular chai, no sugar 3 large chai, with sugar
Parameters with defaults must come after parameters without defaults. def chai_order(sugar=True, cups): fails with "parameter without a default follows parameter with a default".
The mutable default trap
This is one of the most famous Python surprises. A default value is created once, when the def line runs, not every time the function is called. If the default is a mutable object such as a list or dictionary, every call shares the same object.
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item("rice"))
print(add_item("dal"))['rice'] ['rice', 'dal']
The second call should have returned only ['dal'], but the rice from the first call is still in the shared list. The fix is to use None as the default and create a fresh list inside the function:
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
print(add_item("rice"))
print(add_item("dal"))['rice'] ['dal']
Use this None pattern for any default that is a list, dictionary or set. Numbers, strings, True, False and None are immutable, so they are always safe defaults.
Any number of arguments: *args and **kwargs
Put a star before a parameter name, as in *prices, and that parameter collects all extra positional arguments into a tuple. By convention it is often called *args, but a meaningful name is better.
def total_bill(*prices):
print("Received:", prices)
return sum(prices)
print(total_bill(120, 45, 300))
print(total_bill())Received: (120, 45, 300) 465 Received: () 0
Two stars, as in **details, collect all extra keyword arguments into a dictionary. The usual name is **kwargs (short for keyword arguments).
def make_profile(name, **details):
print(name)
for key, value in details.items():
print(f" {key}: {value}")
make_profile("Kabir", city="Pune", course="BCA", year=2)Kabir city: Pune course: BCA year: 2
Keyword-only and positional-only parameters
Sometimes a value is so easy to confuse that callers should always name it. Any parameter after a bare * is keyword-only: it can only be passed as name=value.
The opposite also exists. Parameters before a / are positional-only: callers cannot use their names. This is useful when the names are not meaningful or may change later. Many built-in functions work this way; for example len() does not accept len(obj="Pune").
def transfer(amount, /, to_account, *, note=""):
print(f"Sending ₹{amount} to {to_account} {note}".strip())
transfer(500, "SBI-1234")
transfer(2000, to_account="HDFC-5678", note="(rent)")
try:
transfer(500, "SBI-1234", "gift")
except TypeError as error:
print("Error:", error)Sending ₹500 to SBI-1234 Sending ₹2000 to HDFC-5678 (rent) Error: transfer() takes 2 positional arguments but 3 were given
Here amount must be positional, to_account can be either, and note must be named. The try/except lets the program show the error instead of stopping; you will learn it in Errors & Exceptions.
When you combine everything, the order inside the brackets is always the same:
| Part | Example | How callers pass it |
|---|---|---|
| Positional-only | a, b, / | By position only |
| Normal | c, d=0 | By position or by name |
| Extra positional | *args (or a bare *) | Collected into a tuple |
| Keyword-only | e, f=1 | By name only |
| Extra keyword | **kwargs | Collected into a dict |
Unpacking arguments in a call
Stars also work the other way round. In a call, * spreads a list or tuple into separate positional arguments, and ** spreads a dictionary into keyword arguments.
def book_ticket(passenger, source, destination):
print(f"{passenger}: {source} to {destination}")
trip = ["Meera", "Kochi", "Chennai"]
book_ticket(*trip)
details = {"passenger": "Farhan", "source": "Lucknow", "destination": "Agra"}
book_ticket(**details)Meera: Kochi to Chennai Farhan: Lucknow to Agra
Common mistakes
- A list or dict as a default value. It is shared between calls. Use
Noneand create the object inside the function. - Passing the same argument twice.
book_ticket("Isha", passenger="Riya", ...)raisesTypeError: ... got multiple values for argument 'passenger'. - Misspelling a keyword.
chai_order(2, suger=False)raisesTypeErrorbecause there is no parameter calledsuger. - Thinking
*argsis a list. It is a tuple, so you cannotappend()to it. Convert it withlist(args)if you need to change it.
Key points
- Positional arguments match by order; keyword arguments match by name and must come after positional ones.
- Default values are used when an argument is left out, and must follow parameters without defaults.
- Never use a mutable object such as
[]or{}as a default; useNoneinstead. *argscollects extra positional arguments into a tuple;**kwargscollects extra keyword arguments into a dict.- Parameters after
*are keyword-only; parameters before/are positional-only. - In a call,
*sequenceand**dictionaryunpack values into arguments.
Exercise
Write a function make_receipt(customer, *amounts, discount=0) for a shop in Rohtak. It should add up all the amounts, subtract discount percent, and return a string such as Aarav: 3 items, total ₹612.00. Make discount keyword-only (the *amounts parameter already does this). Test it with make_receipt("Aarav", 250, 180, 250, discount=10) and with a customer who buys nothing.
Show solution
def make_receipt(customer, *amounts, discount=0):
subtotal = sum(amounts)
total = subtotal - subtotal * discount / 100
return f"{customer}: {len(amounts)} items, total ₹{total:.2f}"
print(make_receipt("Aarav", 250, 180, 250, discount=10))
print(make_receipt("Priya"))Aarav: 3 items, total ₹612.00 Priya: 0 items, total ₹0.00
Because discount comes after *amounts, it can only be given by name, so a caller can never confuse it with a price.
Quick Quiz
1. What does def f(items=[]) risk?
Defaults are created once when def runs, so a mutable default is shared between all calls.
2. Inside def f(*args):, what type is args?
Extra positional arguments are collected into a tuple.
3. In def f(a, *, b):, how must b be passed?
Parameters after a bare * are keyword-only.
4. What does / mean in def f(x, y, /, z):?
Parameters before / are positional-only.
Learn Python with a mentor
Free lessons get you started. Our Python Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
