Returning: one value, several values, or nothing at all
return ends the function immediately
def first_even(numbers):
for n in numbers:
if n % 2 == 0:
return n
return NoneThe return inside the loop stops the loop and the function. Nothing after it runs. That is why the guard-clause style from the branching lesson works so well: each early return removes a case and the remaining code gets simpler rather than more nested.
A function with no return statement, or with a bare return, hands back None. This is not an error; it is a value, and it will show up later as TypeError: 'NoneType' object is not subscriptable if you forget.
Returning several values
def stats(numbers):
return min(numbers), max(numbers), sum(numbers) / len(numbers)
low, high, mean = stats([3, 9, 4])Python has no special syntax here. The function returns one tuple, and the caller unpacks it. If you take only part of it, use _ for the parts you ignore:
_, high, _ = stats(readings)Unpacking fails loudly if the count changes — ValueError: too many values to unpack — which is a feature. A function whose return shape changed will break at the call site rather than silently handing back something wrong.
When a tuple stops being a good idea
At three values, a tuple is fine. At five, callers start writing result[3] and nobody knows what position three is. Two better options:
from typing import NamedTuple
class Stats(NamedTuple):
low: float
high: float
mean: float
count: intStats(1, 9, 4.5, 3) still unpacks like a tuple, still indexes like a tuple, and also supports result.mean. It costs four lines and removes a permanent source of confusion.
Or return a dictionary, which is right when the set of keys is genuinely variable — a parsed API response, for instance — and wrong when it is fixed, because typos in keys are not caught anywhere.
Return, do not print
The distinction from the earlier functions lesson deserves one more pass, because it decides whether your code can be reused.
def report(rows):
for r in rows:
print(f"{r['name']}: {r['total']}")That function can do exactly one thing: write to a terminal. It cannot be tested without capturing standard output, cannot write to a file, cannot be sent over HTTP, and cannot be translated.
def format_report(rows):
return "\n".join(f"{r['name']}: {r['total']}" for r in rows)
print(format_report(rows))Now the same function serves a terminal, a file, an email and a test, and the test is one line: assert format_report([...]) == "...". Push printing to the outermost layer of the program and keep everything underneath returning values.
Returning a value the caller must check
Three ways to signal "there was no answer", each with a cost:
- Return
None. Cheap, and easy to forget to check. Fine when absence is ordinary and the caller obviously handles it. - Return a default.
get_price(item, default=0)keeps the caller simple and can hide a real failure inside an average. - Raise an exception. Impossible to ignore, and correct when continuing would mean producing a wrong answer. The errors module covers this properly.
Whichever you choose, choose it once per function and write it in the docstring. Functions that sometimes return None and sometimes raise are the ones that produce three-in-the-morning bugs.
Never signal failure with a sentinel like -1 or "ERROR". It has the same type as a real answer, so it flows through arithmetic and string joins undetected until the total is wrong.
Returning early against a single exit
Some traditions insist on one return per function. Python's does not, and guard clauses are the house style. The practical test is whether a reader can see all the ways out. Four early returns in a fifteen-line function are clear. Nine returns scattered through eighty lines are not, and that function wanted splitting anyway.
A function that returns nothing should say so
If a function's job is a side effect — writing a file, sending a request — return None and name it with a verb: save_report, send_email, delete_row. Functions that compute get noun-ish names: total, format_report, parse_date. The convention is not decoration; it means a reader can tell from the call site whether the result matters.
The one thing to keep
Return values instead of printing them, because a function that prints can only ever feed a terminal, while a function that returns feeds a file, a test, a request and a terminal.
Before you move on
A price lookup returns `-1` when an item is unknown. Totals across the catalogue have been slightly low for months. What is the defect this illustrates?
Pick the one you would defend. Nobody sees your answer.