Parameters, defaults and keyword arguments
Parameters are the named slots in a function definition; arguments are the actual values you pass when you call it. def format_ticket(ref, title): has two parameters; format_ticket("DEV-1", "Fix") passes two arguments.
You can pass arguments positionally (matched by order) or by keyword (matched by name): format_ticket("DEV-1", title="Fix"). Keyword arguments are clearer at the call site and free you from remembering the order — past the first couple of params, name them.
A parameter can have a default value: priority="medium". Callers who don't pass it get the default; those who do override it. Defaults must come *after* all the non-default parameters in the definition.
There's one notorious trap: the default value is created once, when the function is defined — not fresh on each call. So a mutable default like [] or {} is *shared* across every call that uses it, and changes leak between calls. Use None as the default and build the real list inside.
Syntax
def f(required, optional="default"):
...
f(1) # optional uses its default
f(1, "custom") # positional override
f(1, optional="x") # keyword override (clearer)
# Safe mutable default pattern:
def g(items=None):
if items is None:
items = []Worked examples
Defaults plus keyword overrides
def format_ticket(ref, title, priority="medium", assignee="unassigned"):
return f"[{ref}] {title} ({priority}) — {assignee}"
print(format_ticket("DEV-101", "Fix login"))
# [DEV-101] Fix login (medium) — unassigned
print(format_ticket("DEV-102", "Deploy", priority="high"))
# [DEV-102] Deploy (high) — unassigned
print(format_ticket("DEV-103", "Review", assignee="sam", priority="low"))
# [DEV-103] Review (low) — samThe mutable-default trap (the list is shared!)
def add_item(item, basket=[]): # DON'T: [] made once, reused
basket.append(item)
return basket
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] ← leaked from the last call!The fix: default to None, build inside
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
print(add_item("a")) # ['a']
print(add_item("b")) # ['b'] ← fresh list each callCommon mistakes & gotchas
Mutable default arguments are shared
A default of [], {} or set() is created once and reused on every call that relies on it, so state leaks between calls. Default to None and create the real container inside the function. This is one of Python's most famous footguns.
Defaults must come after non-defaults
def f(a=1, b): is a SyntaxError — once a parameter has a default, every parameter after it must too. Put all required parameters first.
Keyword can't come before positional at the call
When calling, positional arguments must precede keyword ones: f(x=1, 2) is a SyntaxError. Write f(2, x=1) — positionals first, then keywords.
Why it matters
Defaults and keyword arguments are how functions stay flexible without becoming a wall of required arguments. Real APIs lean on them constantly — sensible defaults for the common case, named overrides for the rare one — and the mutable-default bug is a genuine production crash you'll be glad you saw coming.