Python Function Arguments: Positional, Keyword and Default
Learn Python function arguments: positional vs keyword, default values, the mutable default trap, keyword-only and positional-only parameters, with examples.
Picture a tailor's shop near Khajrana that takes orders for kurtas. The order slip has boxes for customer name, chest size, length, fabric and delivery date. Most customers just say their name and measurements in the usual order.
Some prefer to say "fabric cotton, length 42" in whatever order comes to mind. Delivery date is left blank by most people, in which case the shop writes "7 days" by default.
Those three habits, filling boxes in order, naming the box you mean, and leaving some boxes to their usual value, are exactly how Python passes values into a function.
Filling in order is a positional argument. Saying "fabric = cotton" is a keyword argument. The pre-printed "7 days" is a default value. The order slip itself, with its labelled boxes, is the function's parameter list.
Function arguments in Python are the values passed into a function call, matched to the function's parameters either by position or by keyword, with default values filling in any optional parameters left out.
The previous lesson, Python functions, covered def and return. This one is about the brackets: every way to get values in, and the rules Python enforces while matching them. All examples run on Python 3.12+.
Positional vs keyword arguments
Positional arguments are matched to parameters left to right. Keyword arguments are matched by name, so their order does not matter. You can mix both in one call, as long as every positional argument comes before the first keyword argument.
def invoice_line(product, qty, price):
return f"{product:<10} {qty:>3} x {price:>5} = {qty * price:>6}"
print(invoice_line("Kurta", 3, 899)) # positional
print(invoice_line(price=1499, product="Sherwani", qty=1)) # keyword, any order
print(invoice_line("Dupatta", price=349, qty=4)) # mixedThe one ordering rule Python will not bend is that positional arguments come first. Put a positional value after a keyword one and Python refuses to run the file at all, before a single line executes:
invoice_line(product="Kurta", 3, 899)
SyntaxError: positional argument follows keyword argumentOther mismatches only show up when the call actually runs, as a TypeError. This block triggers four of them on purpose.
def invoice_line(product, qty, price):
return f"{product} x{qty} = {qty * price}"
bad_calls = [
lambda: invoice_line("Kurta", 3), # missing price
lambda: invoice_line("Kurta", 3, 899, 10), # one too many
lambda: invoice_line("Kurta", 3, 899, qty=2), # qty given twice
lambda: invoice_line("Kurta", 3, cost=899), # no such parameter
]
for call in bad_calls:
try:
call()
except TypeError as err:
print("TypeError:", err)Each message names the problem precisely, so read it before guessing. The tiny lambda: wrappers just delay each call so the loop can try them one by one; Python lambda explains them.
Positional values fill parameters from the left, keyword values jump to their named slot, and defaults fill whatever is left.
When should you use keyword arguments?
Use keywords whenever the meaning of a value is not obvious from the value itself. report(2026, 11, True, False) tells a reader nothing. report(year=2026, month=11, include_returns=True, send_email=False) reads like a sentence and survives someone reordering the parameters later.
How do default argument values work?
Give a parameter a value in the definition, such as gst=0.18, and callers may leave it out. Parameters with defaults must come after those without, because Python fills positional values left to right.
def final_price(amount, gst=0.18, discount=0):
return round(amount * (1 + gst) - discount)
print(final_price(1000)) # both defaults
print(final_price(1000, 0.05)) # gst given by position
print(final_price(1000, discount=100)) # skip gst, set discount
print(final_price(1000, gst=0, discount=50))Keyword arguments let you skip over a default in the middle, as the third call does. Without them, you would have to repeat 0.18 just to reach discount.
When are default values evaluated?
Exactly once, when the def statement runs, not on every call. For numbers, strings and None this makes no difference. For a list or dictionary it causes one of Python's best-known bugs.
Quick check
def price(amount, gst=0.18, discount=0). Which call sets discount to 50 and keeps the default gst?
The keyword skips gst. The first call sets gst to 50, and the third is a SyntaxError because a positional argument follows a keyword one.
The mutable default argument trap
Suppose you write a helper that adds a tag to a customer's tag list, creating a new list when none is passed.
def add_tag(tag, tags=[]): # BUG: one list shared by every call
tags.append(tag)
return tags
print(add_tag("premium"))
print(add_tag("emi")) # expected ['emi']
print(add_tag("repeat"))
def add_tag_fixed(tag, tags=None): # FIX: create a new list per call
if tags is None:
tags = []
tags.append(tag)
return tags
print(add_tag_fixed("premium"))
print(add_tag_fixed("emi"))The buggy version keeps growing: ['premium'], then ['premium', 'emi'], then all three. The empty list was created once, at definition time, and every call that relies on the default appends to that same list.
A list default lives on the function object and is reused; a None default lets each call build its own list.
The fix is a standard idiom: default to None, then create the list inside the function. Use the same pattern for dictionaries and sets. Many linters flag def f(x=[]) for exactly this reason.
Arguments are references to objects
Python does not copy arguments. The parameter becomes a new name for the same object the caller passed. What happens next depends on whether the object can change.
def apply_discount(prices, pct):
for i in range(len(prices)):
prices[i] = round(prices[i] * (1 - pct / 100))
return prices
def apply_discount_safe(prices, pct):
return [round(p * (1 - pct / 100)) for p in prices]
shop = [1000, 2500, 400]
new = apply_discount_safe(shop, 10)
print("safe:", shop, new)
apply_discount(shop, 10)
print("in place:", shop) # the caller's list changed
city = "indore"
def shout(s):
s = s.upper() # rebinding: only the local name changes
return s
print(shout(city), city)Changing a mutable object in place, such as setting prices[i], is visible to the caller. Rebinding the parameter to a new object, as s = s.upper() does, is not. For analysis code, returning a new list is usually safer than editing the caller's data in place. The lists article explains why two names can share one list.
Keyword-only and positional-only parameters
Two special markers in a parameter list let you control how callers may pass values.
| Marker | Meaning | Example definition | Valid call |
|---|---|---|---|
* |
Parameters after it must be passed by keyword | def export(df, *, sep=",") |
export(data, sep=";") |
/ |
Parameters before it must be passed by position | def ratio(a, b, /) |
ratio(3, 4) |
| both | Mix of the two | def f(a, /, b, *, c) |
f(1, 2, c=3) or f(1, b=2, c=3) |
def monthly_report(month, year, *, include_returns=False, currency="INR"):
flag = "with returns" if include_returns else "net of returns"
return f"{month}/{year} report, {flag}, in {currency}"
print(monthly_report(11, 2026, include_returns=True))
try:
monthly_report(11, 2026, True) # flag passed by position
except TypeError as err:
print("TypeError:", err)
def growth(old, new, /):
return round((new - old) / old * 100, 1)
print(growth(180000, 205500))Keyword-only flags are a gift to future readers: nobody can write a mysterious True in a call any more. Positional-only parameters, written with / since Python 3.8, are common in built-ins such as len(obj, /). They let a library rename a parameter later without breaking callers.
What about the * in *args? A bare * collects nothing; it is just a marker. A named *args collects any extra positional values into a tuple, which is the topic of the next lesson, Python *args and **kwargs.
Quick check
In def send(to, *, subject), how must subject be passed?
Every parameter after a bare * is keyword-only, so the call must say subject=...
Common mistakes with function arguments
- Mutable defaults.
def f(items=[])shares one list across calls. Default toNone. - Positional after keyword.
f(a=1, 2)is aSyntaxError. Put positional values first. - Non-default before default in the definition.
def f(a=1, b)is aSyntaxError. - Passing the same parameter twice.
f(1, a=2)whereais the first parameter raisesTypeError. - Unreadable boolean flags.
report(True, False)hides intent. Make flags keyword-only. - Silently changing the caller's list. Return a new list unless in-place editing is the documented purpose.
Key takeaways
- Positional arguments match by order; keyword arguments match by name.
- In a call, all positional arguments come before keyword arguments.
- Defaults make parameters optional and are evaluated once, at
deftime. - Use
Noneas the default for lists, dicts and sets, and create them inside. - Functions receive references, so in-place changes to mutable arguments affect the caller.
*makes later parameters keyword-only;/makes earlier ones positional-only.
FAQ
What are the types of function arguments in Python?
Python has positional arguments, matched by order; keyword arguments, matched by name; and default arguments, which are optional because the definition supplies a value. Functions can also collect any number of extra arguments with *args and **kwargs, and use * and / to require keyword-only or positional-only passing.
What is the difference between positional and keyword arguments?
Positional arguments are assigned to parameters in the order they appear in the call. Keyword arguments name the parameter, as in qty=3, so their order does not matter. Keyword arguments make calls clearer and let you skip defaults, but they must come after all positional arguments.
Why should you not use a list as a default argument in Python?
Python evaluates default values once, when the function is defined, so a list default is a single object shared by every call. Appending to it in one call leaves the item there for the next call. Use None as the default and create a new list inside the function instead.
Does Python pass arguments by value or by reference?
Neither exactly. Python passes references to objects, often called "pass by object reference". The function can change a mutable object, such as a list, and the caller will see it. Rebinding the parameter to a new object inside the function does not affect the caller's variable.
Is there a classroom Python course for fresh graduates in Indore?
Yes. Debugshala runs data analytics batches for fresh graduates at its Old Palasia campus in Indore, starting with Python fundamentals. Classes are in English and Hindi, a free demo comes first, and you can check your grasp of arguments on Debugshala Test.
From the team behind this site
Go further with the Data Analytics Course at Debugshala
These tutorials are written by Debugshala, a training and placement institute at Old Palasia, Indore. The classroom course adds live mentoring, real projects and interview preparation.
- Offline classes at Old Palasia, Indore
- Free demo class before you pay
- Non-IT, fresher and year-gap friendly
- Placement assistance
700+ students placed · 200+ hiring partners · 4.9★ Google rating