Python Tutorial
Function Arguments in Python
Python functions have one of the most flexible parameter systems of any language: positional and keyword arguments, default values, variable numbers of arguments with *args and **kwargs, keyword-only and positional-only parameters, and argument unpacking at the call site.
This lesson covers every kind of parameter, the order they must appear in, the mutable-default-argument trap, and how to design clear function signatures.
Positional, Keyword and Default Arguments
Arguments can be passed by position (area(3, 4)) or by name (area(width=3, height=4)), and keyword arguments can come in any order. Parameters with default values (def greet(name, greeting="Hello")) become optional. Parameters with defaults must come after those without.
*args and **kwargs
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. They let functions accept any number of values and are used for wrappers and decorators that pass arguments through. The names are conventions — only the stars matter.
Keyword-Only and Positional-Only Parameters
Parameters after * (or after *args) are keyword-only: callers must name them, which makes calls like send(email, retry=True) self-documenting. Parameters before / are positional-only: callers cannot use their names, which lets you rename them later. The full order is: positional-only, /, normal, *args or *, keyword-only, **kwargs.
Unpacking and the Mutable Default Trap
At the call site, *sequence spreads items as positional arguments and **mapping spreads a dict as keyword arguments. Default values are evaluated once, when the function is defined — so a default like items=[] is shared between calls. Use None as the default and create the list inside the function.
Examples
Positional, keyword and default arguments
def order_summary(item, quantity=1, price=100.0, currency="INR"):
return f"{quantity} x {item} = {quantity * price:.2f} {currency}"
print(order_summary("pen"))
print(order_summary("book", 3, 250))
print(order_summary("bag", price=899, quantity=2))
print(order_summary(currency="USD", item="mug", price=9.5))
1 x pen = 100.00 INR
3 x book = 750.00 INR
2 x bag = 1798.00 INR
1 x mug = 9.50 USD
*args and **kwargs
def total(*numbers):
print("numbers is a", type(numbers).__name__, numbers)
return sum(numbers)
def create_user(username, **details):
print("details is a", type(details).__name__, details)
return {"username": username, **details}
print(total(10, 20, 30))
print(create_user("asha", city="Pune", age=24))
def log_call(func, *args, **kwargs):
print(f"calling {func.__name__} with {args} {kwargs}")
return func(*args, **kwargs)
print(log_call(round, 3.14159, ndigits=2))
numbers is a tuple (10, 20, 30)
60
details is a dict {'city': 'Pune', 'age': 24}
{'username': 'asha', 'city': 'Pune', 'age': 24}
calling round with (3.14159,) {'ndigits': 2}
3.14
Keyword-only and positional-only parameters
def send_email(to, subject, /, *, cc=None, urgent=False):
return f"to={to} subject={subject} cc={cc} urgent={urgent}"
print(send_email("a@x.in", "Hi", urgent=True))
try:
send_email("a@x.in", "Hi", None, True)
except TypeError as e:
print("TypeError:", e)
try:
send_email(to="a@x.in", subject="Hi")
except TypeError as e:
print("TypeError:", e)
to=a@x.in subject=Hi cc=None urgent=True
TypeError: send_email() takes 2 positional arguments but 4 were given
TypeError: send_email() got some positional-only arguments passed as keyword arguments: 'to, subject'
Argument unpacking and the mutable default trap
def point(x, y, z=0):
return (x, y, z)
coords = [1, 2, 3]
options = {"x": 5, "y": 6}
print(point(*coords), point(**options))
def add_item_bad(item, cart=[]):
cart.append(item)
return cart
def add_item_good(item, cart=None):
if cart is None:
cart = []
cart.append(item)
return cart
print(add_item_bad("pen"), add_item_bad("book"))
print(add_item_good("pen"), add_item_good("book"))
(1, 2, 3) (5, 6, 0)
['pen', 'book'] ['pen', 'book']
['pen'] ['book']
Common Mistakes
- Using a mutable default argument like [] or {}.
- Putting a parameter without a default after one with a default (SyntaxError).
- Passing boolean flags positionally (send(x, True, False)) — make them keyword-only.
- Overusing **kwargs so that nobody can tell what a function accepts.
Key Points to Remember
- Arguments can be positional or keyword; defaults make parameters optional.
- *args collects extra positionals (tuple); **kwargs extra keywords (dict).
- Parameters after * are keyword-only; before / are positional-only.
- *seq and **dict unpack arguments at the call site.
- Use None, not [] or {}, as a default for mutable values.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.