Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

Python, From Zero, For AI

From your first line of code to your first API call.

Lesson 24 of 897 min

Returning: one value, several values, or nothing at all

return ends the function immediately

python
def first_even(numbers):
    for n in numbers:
        if n % 2 == 0:
            return n
    return None

The 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

python
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:

python
_, 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:

python
from typing import NamedTuple

class Stats(NamedTuple):
    low: float
    high: float
    mean: float
    count: int

Stats(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.

python
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.

python
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.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly

Returning: one value, several values, or nothing at all · Python, From Zero, For AI · Addaly