FunctionsChapter 49 of 114
Arguments
Positional and keyword arguments, and how Python matches them up.
Positional arguments
Matched by order:
def describe(name, age):
print(f"{name} is {age}")
describe("Ada", 36)Output
Ada is 36
Get the order wrong and you get nonsense rather than an error:
def describe(name, age):
print(f"{name} is {age}")
describe(36, "Ada")Output
36 is Ada
Keyword arguments
Naming them removes the ordering problem entirely:
def describe(name, age):
print(f"{name} is {age}")
describe(age=36, name="Ada")Output
Ada is 36
Keyword arguments must come after positional ones:
def describe(name, age):
print(f"{name} is {age}")
describe("Ada", age=36)Output
Ada is 36
Booleans are the worst offenders. save(data, True) tells the reader nothing; save(data, overwrite=True) tells them everything.
Forcing the style
A * in the parameter list means everything after it must be passed by keyword:
def connect(host, *, timeout=10, retries=3):
print(host, timeout, retries)
connect("example.com", timeout=5)
try:
connect("example.com", 5)
except TypeError as problem:
print("TypeError:", problem)Output
example.com 5 3 TypeError: connect() takes 1 positional argument but 2 were given
A / does the opposite, marking parameters before it as positional-only. You will see it in the standard library more often than you will write it.
Arguments are passed by assignment
The parameter becomes another name for the same object. What that means depends on whether the object can be changed.
Rebinding inside the function does not affect the caller:
def rename(name):
name = "changed"
value = "original"
rename(value)
print(value)Output
original
But changing a mutable object in place does:
def add_item(items):
items.append("new")
values = ["first"]
add_item(values)
print(values)Output
['first', 'new']
Both are the same rule: the name inside is a new label on the same object. Assigning moves that label; mutating changes the object everyone can see.
The mutable default trap
This one catches nearly everyone once, and it is worth meeting on purpose:
def add(item, target=[]):
target.append(item)
return target
print(add("a"))
print(add("b"))Output
['a'] ['a', 'b']
The default is created once, when the function is defined, not on each call. Every call that relies on the default shares the same list.
The fix is always the same:
def add(item, target=None):
if target is None:
target = []
target.append(item)
return target
print(add("a"))
print(add("b"))Output
['a'] ['b']
Unpacking into arguments
* spreads a sequence into positional arguments and ** spreads a dictionary into keyword ones:
def describe(name, age):
print(f"{name} is {age}")
values = ["Ada", 36]
describe(*values)
details = {"name": "Grace", "age": 45}
describe(**details)Output
Ada is 36 Grace is 45
Test yourself
2 questionsWhat is wrong with 'def add(item, target=[])'?
Show the answer
The list is created once and shared by every call — Use None as the default and build the real list inside the function.
What does the do in 'def connect(host, , timeout=10)'?
Show the answer
Forces everything after it to be passed by keyword — It is a good way to stop callers writing connect(host, 5) where 5 means nothing to a reader.
args and kwargs
Accept any number of positional or keyword arguments.