A function gives a name to a piece of behaviour so you can use it more than once, and so a reader can understand your program without reading every line of it.
Warm-up: from Days 1 to 3
From memory first — the effort of recalling is the point.
1. Predict both lines.
prices = {"tea": 2.5, "cake": 4.0}
prices["tea"] += 0.5
order = ["tea", "cake", "tea"]
total = 0
for item in order:
total += prices[item]
print(total)
print(sorted(set(order)))Check your prediction
10.0
['cake', 'tea']Tea costs 3.0 after the update, and it was ordered twice: 3.0 + 4.0 + 3.0.
The set drops the second "tea", and sorted puts the rest in order.
2. Fix. This should print each name with its score.
scores = {"ada": 91, "alan": 78}
for name, score in scores:
print(f"{name}: {score}")Check your fix
scores = {"ada": 91, "alan": 78}
for name, score in scores.items():
print(f"{name}: {score}")ada: 91
alan: 78Looping over a dictionary gives only its keys. Python then tried to unpack the
key "ada" — three letters — into two names, and complained. .items() gives
the pairs.
3. Write. Make a new list of the temperatures above 20 and print it:
[24, 31, 27].
temps = [18, 24, 31, 27, 15]Check your answer
temps = [18, 24, 31, 27, 15]
warm = []
for t in temps:
if t > 20:
warm.append(t)
print(warm)[24, 31, 27]Start empty, loop, append the ones you want. You’ll write this pattern in one line by the end of today.
Define, then call
def greet(name):
return f"Hello, {name}!"
print(greet("Ada"))
print(greet("Alan"))
message = greet("Grace")
print(message.upper())The anatomy: def, the name, the parameters in parentheses, a colon, then
an indented body. return hands a value back to whoever called it.
return, not print
This distinction is worth getting right on day one of functions.
def add_printing(a, b):
print(a + b) # shows the answer
def add_returning(a, b):
return a + b # hands the answer back
add_printing(2, 3)
result = add_printing(2, 3)
print("captured:", result) # None - nothing was returned
result = add_returning(2, 3)
print("captured:", result)
print(add_returning(2, 3) * 10) # usable in a bigger expressionA function that prints can only ever put text on a screen. A function that returns can be used in arithmetic, stored, tested, and printed if you want to. Return values; print at the edges of your program.
Every function returns something
A function with no return returns None. That is why print(add_printing(2, 3))
shows None — you are printing the absence of a result.
Several parameters, and defaults
A parameter can have a default value, which makes it optional at the call site.
def price_with_tax(amount, rate=0.2):
return round(amount * (1 + rate), 2)
print(price_with_tax(100)) # uses the default rate
print(price_with_tax(100, 0.05)) # overrides it
def greet(name, greeting="Hello", punctuation="!"):
return f"{greeting}, {name}{punctuation}"
print(greet("Ada"))
print(greet("Ada", "Welcome"))
print(greet("Ada", punctuation="?")) # skip the middle one by namingParameters with defaults must come after those without — Python has no way to guess otherwise.
Never default to a list
def add_item(item, basket=[]): looks reasonable and is a genuine trap: the
default list is created once, when the function is defined, and is shared by
every call that uses it. Use basket=None and create a fresh list inside.
def broken(item, basket=[]):
basket.append(item)
return basket
print(broken("apple"))
print(broken("pear")) # the apple is still there
def fixed(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
print(fixed("apple"))
print(fixed("pear"))Docstrings
A string on the first line of a function is its documentation. Tools read it,
help() prints it, and your future self will thank you.
def body_mass_index(weight_kg, height_m):
"""Return the BMI for a weight in kilograms and a height in metres.
The result is rounded to one decimal place.
"""
return round(weight_kg / height_m ** 2, 1)
print(body_mass_index(70, 1.75))
help(body_mass_index)Say what it returns and what the arguments mean. Do not restate the code.
Functions calling functions
Small functions that call each other is how programs stay readable as they grow.
def is_vowel(letter):
return letter.lower() in "aeiou"
def count_vowels(text):
count = 0
for letter in text:
if is_vowel(letter):
count += 1
return count
def describe(text):
vowels = count_vowels(text)
return f"{text!r} has {vowels} vowels out of {len(text)} characters"
print(describe("Programming"))
print(describe("rhythm"))Each function does one thing and is testable on its own. {text!r} in the
f-string means “show the repr” — the quoted, debugging-friendly form.
Before you write your own
Predict. Three lines are printed. Watch which function prints and which returns — and the order things happen in.
def double(n):
return n * 2
def shout(word):
print(word.upper())
x = double(4)
y = shout("hi")
print(x, y)
print(double(double(3)))Check your prediction
HI
8 None
12HI comes first because shout prints the moment it’s called — on the
y = ... line. It returns nothing, so y is None. And a returned value can go
straight into another call: double(3) is 6, and double(6) is 12.
Fix. The average should be usable in the f-string, giving Average: 4.0.
def average(values):
print(sum(values) / len(values))
result = average([3, 4, 5])
print(f"Average: {result}")Check your fix
def average(values):
return sum(values) / len(values)
result = average([3, 4, 5])
print(f"Average: {result}")Average: 4.0The original printed 4.0 itself and then handed back None. Return the value
and let the caller decide what to do with it.
A tiny statistics toolkit
Write three functions — mean(values), largest(values) and
range_of(values) (the difference between the largest and smallest) — then use
them to report on the list. Each must return, not print. The numbers you
should get:
Mean: 22.5
Largest: 30
Range: 12readings = [18, 21, 25, 19, 30, 22]
# Your code hereHint 1: the idea
Three small functions, each taking the whole list as its one parameter and
returning one number. Inside them, sum, len, max and min do the work.
The report is then three print calls that use the functions.
Hint 2: a first step
Write only mean first, and check print(mean(readings)) shows 22.5 before
you start the others. A mean is the total divided by how many values there are.
Show one solution
readings = [18, 21, 25, 19, 30, 22]
def mean(values):
"""Return the arithmetic mean of a non-empty sequence of numbers."""
return sum(values) / len(values)
def largest(values):
"""Return the biggest value."""
return max(values)
def range_of(values):
"""Return the spread between the largest and smallest values."""
return max(values) - min(values)
print(f"Mean: {mean(readings):.1f}")
print(f"Largest: {largest(readings)}")
print(f"Range: {range_of(readings)}")largest is a thin wrapper over max and would not be worth writing in real
code — but naming the operations you care about is exactly how a domain
vocabulary starts.
What you learned
def name(parameters):defines a function; calling it runs the body.returnsends a value back; without it you getNone.- Return values rather than printing them, so callers can use the result.
- Default parameters make arguments optional — never default to a mutable value.
- A docstring documents what the function returns and what its arguments mean.