Why this matters
A function often has more than one answer. A function that looks through a list of scores wants the smallest score and the largest score. A function that divides one number by another wants the quotient and the remainder. A function that reads a piece of text wants to know whether the text was valid and, if it was, the number inside it.
In Python, one return line can give back several values, such as return smallest, largest. Python packs them into one tuple. A tuple is a fixed group of values kept in order. This lesson shows how that works, when a named result is clearer, and why the holder pattern from other languages is usually the wrong choice. Python has no keyword for an out parameter, so the holder pattern is the one way to get that effect.
This lesson builds on functions, parameters and return values, which the earlier lessons in this module cover. Pure functions, where a function depends only on its inputs, come in a later lesson.
One return value, many answers
Start with a plain example. You want a function that returns the smallest and largest value in a list of numbers. The function can return both values in one line:
Output:
45 91
(45, 91)The comma in return smallest, largest makes a tuple. The parentheses in the second line of output show that the function gave back one value. The line low, high = min_max(...) is called unpacking. Python takes each item of the tuple and gives it a name. The number of names on the left must match the number of items on the right, and the common mistakes section shows what happens when it does not.
There are four common ways to return two or three answers:
- Return a tuple, such as
(smallest, largest), and let the caller unpack it. - Return a dictionary, which holds named keys and values.
- Return a dataclass, which is a small class whose only job is to hold named values.
- Pass in a list and have the function fill it in, then return nothing. This is the holder pattern.
The first option works, but the names are lost. A reader sees result[0] and must look up what it means. The holder pattern is covered later in this lesson, and it is usually the weakest choice. The dataclass gives each answer a name, so this lesson uses it for most examples.
The simplest answer: a named result
A dataclass is a short way to write a class whose only job is to hold some named values. Put @dataclass on the line above the class, and list the names with their types inside the class. The line that starts with @ is a decorator. A decorator changes the class below it. Here it adds the code that stores the values when you create an object.
from dataclasses import dataclass
@dataclass(frozen=True)
class MinMax:
smallest: int
largest: intThe : int after each name is a type hint. It says what kind of value the field should hold. Python does not check the type when the program runs, but the hint tells a reader what the code expects. The frozen=True part means the values cannot be changed after the object is made. Trying to change one raises an error called FrozenInstanceError.
You create one with MinMax(45, 91). You read a field with a dot and no parentheses: r.smallest gives the first value. If you know Java records, the dataclass is the close match. The one difference is that Python reads a field as r.smallest, with no parentheses, where Java calls an accessor method such as r.smallest().
Printing the object shows the names and values: print(r) prints MinMax(smallest=45, largest=91). The dataclass is part of the standard library, so you do not install anything to use it.
The rest of this lesson uses dataclasses for return values. The class name says what the result is, and the function returns it.
Example 1: the smallest and the largest score
The first example finds two answers from one loop. The function starts with the first value as both the smallest and the largest. Then it looks at every value and updates either answer when it finds a better one.
Output:
smallest = 45
largest = 91
MinMax(smallest=45, largest=91)Read the function from top to bottom. The two names smallest and largest hold the working answers. The loop walks through the list once. The last line builds the dataclass from the two names and returns it. The caller gets one value, r, and reads the fields it needs.
Notice that smallest starts at values[0], not at zero. If it started at zero, a list of all positive scores would report a smallest value of zero, which is wrong. This is a common bug when the first answer must come from the data itself.
Example 2: the quotient and the remainder
Some results come in pairs by nature. Integer division gives a quotient, and the remainder is what is left over. When you divide 17 by 5, the quotient is 3 and the remainder is 2, because 5 times 3 is 15 and 17 minus 15 is 2. Python has a built-in function, divmod, that returns both values in one tuple.
Output:
3 remainder 2
(3, 2)
(3, 2)
(-4, 3)The last line shows a detail of Python that you must know. The // operator rounds down, toward negative infinity. So -17 // 5 gives -4, not -3. The remainder then has to match, and -17 % 5 gives 3. The rule is that a equals (a // b) * b + a % b. Here that is 5 * -4 + 3, which is -17. In Python a remainder always has the same sign as the divisor. If you know Java, this is the main surprise: Java rounds toward zero and gives -3 and -2 for the same numbers.
The tuple lets the caller unpack the two answers with quotient, remainder = divide(17, 5). The caller picks the names, not the function. A reader who sees divide(17, 5) without a name has to check the function to learn which value comes first. A dataclass with quotient and remainder fields puts the names in the code itself.
Example 3: a count and the longest word
The third example returns a number and a piece of text. The function counts the words in a list and finds the longest one. The dataclass can hold a number and a string just as easily as two numbers.
Output:
WordStats(count=3, longest='elephant')
elephant has 8 lettersThe function starts longest as an empty string. Any word has more letters than an empty string, so the first word always takes its place. When two words have the same length, the first one wins, because the check uses > and not >=. That tie rule is a choice you make. A comment should say so if the choice matters to a reader.
The dataclass stores the number and the word. It does not store the list itself, so the caller gets only the two answers the function was asked for.
Choosing good names
The field names are part of the design. Use names that describe the answer, not the function. MinMax with smallest and largest says what it holds. A class named Result with fields called a and b gives the reader nothing. Name each field for the value it holds, so that r.smallest reads like a sentence.
Keep results small. Two to four fields is a good size. If a result needs many fields, the function may be doing too much, and it is better to split the function into smaller ones.
Holder pattern: passing in a list to fill
Some languages, such as C# and C++, let a function change a variable that the caller gave it. The changed variable becomes the second answer. This is called an out parameter. A function that parses text might write the number into a variable the caller passed in, and return True or False to say whether the parse worked.
Python has no keyword for this. Python passes each argument as a reference, which means the function receives a label that points at the same object the caller has. The effect depends on the kind of value:
- For a list, a function gets the same list. If the function changes an item in that list, the caller sees the change.
- For a number, a string or a tuple, the value cannot change in place. An assignment inside the function only moves the function's label to a new value. The caller's label still points at the old value.
So a function can change a list the caller passed in, but it cannot replace the caller's number.
The list holder workaround
A programmer who wants an out parameter in Python usually passes in a list with one item. The function writes into that item, and the caller reads it after the call.
Output:
ok = True, value = 42
forgot to check: value = 0The first call works. The function writes 42 into the list and returns True. The second call fails. The function returns False, but the list keeps its old value, which is 0. The program prints 0, and nothing in the output says that the parse failed. The caller had to remember to check the return value, and the second call shows what happens when the caller forgets.
Here, try runs code that might fail, and except ValueError runs only when int() fails. int("abc") raises a ValueError because the text is not a whole number.
The word holder is a plain name for this trick. Python has no special keyword for it. The trick works only because a list is an object that the function can change.
Why the holder is usually the wrong choice
The holder pattern works, and you will see it in older code. For new code it is usually the wrong choice, for four reasons.
The first reason is that the caller must do work before the call. It must create the list with one spot in it, and keep the list around until it reads the answer. A function that returns a value needs none of that setup. The caller writes parsed = parse(text) and is done.
The second reason is that the list can be left half-written. A function that fails halfway may have written one holder and not another. The caller then sees a mix of old and new values, and no single flag says which parts are valid. A returned object is built in full or not built at all, because the function returns it only at the end.
The third reason is that the holder hides the answer from the reader. In a call like parse_into("42", holder), the answer is not on the left side of the line. The reader has to find the holder, see what the function wrote into it, and work out the order. A call like parsed = parse("42") shows the answer in the code.
The fourth reason is that the holder is shared. Any other part of the program that holds the same list sees its changes, which makes the program harder to follow. A returned object is made by one call and given to one caller.
There are cases where a function must change something the caller owns. A function that sorts a list in place does this on purpose. The method list.sort() is one example. The name should make the change clear. The mistake is to use a holder when the answer is small and a return value would do.
Here is the dataclass version of the same parse. The function returns one value that says whether it worked and what the number was.
Output:
Parsed(ok=True, value=42)
Parsed(ok=False, value=0)The caller now has a value that carries its own status. The caller can write if p.ok: before using p.value. The value 0 in the failure case is a placeholder. A reader who sees ok is False knows not to trust it.
When the only second answer is "it failed", Python code often returns None, which is Python's word for no value. Return None when a function finds no answer, and say so in the docstring. The function first_even in the exercises does this. When there are two or more real answers, such as a quotient and a remainder, a tuple or a dataclass is the better tool.
Choosing between the options
Here is a short guide for picking what a function returns. Use it as a starting point, not as a rule that replaces thinking.
| Situation | Good choice |
|---|---|
| Two answers in a fixed order that are easy to read, such as a quotient and a remainder | A tuple, such as return quotient, remainder |
| Two or three named answers that need clear names | A dataclass |
| One answer that might be missing | Return None and say so in the docstring |
| A large change to data the caller owns, such as sorting a list | Change the data in place and say so in the name, such as sort |
| A function that must answer many values of one type | A list, returned as one value |
The table shows that the holder is not on the list. It is almost never the first choice in Python.
Think about what the caller sees. A caller reads a function name and its return line, and then uses the value. If the result has clear field names, the caller needs no further reading. If the function hides its answers in a holder, the caller must read the function body too. This is the main reason to prefer a return value.
Common mistakes
Each error below comes from Python 3.14. A traceback also shows file names and line numbers. Those lines are left out here, and the last line names the problem.
Unpacking the wrong number of values
def min_max(values):
return min(values), max(values), len(values)
low, high = min_max([3, 9, 1])Output (last line of the traceback):
ValueError: too many values to unpack (expected 2, got 3)The function returns three values, and the caller names only two. Add a third name, or make the function return only the two answers the caller needs.
Python does not have return types to check the count, so the mistake shows up only when the line runs. A Java compiler would catch a return type that does not match. Python finds it at runtime.
Forgetting the return statement
def first_even(xs):
for x in xs:
if x % 2 == 0:
return x
result = first_even([1, 3])
print(result[0])Output (last line of the traceback):
TypeError: 'NoneType' object is not subscriptableA function with no return that runs to the end gives back None. The loop never finds an even number, so the function ends without a return, and the caller gets None. Indexing None fails. Add return None at the end of the function, and check for None before you use the result.
Calling a field as if it were a method
from dataclasses import dataclass
@dataclass(frozen=True)
class MinMax:
smallest: int
largest: int
r = MinMax(45, 91)
print(r.smallest())Output (last line of the traceback):
TypeError: 'int' object is not callableA field is read without parentheses, so the fix is r.smallest. This is the reverse of a Java habit, where the parentheses are needed for an accessor method.
Passing an int where the function needs a list
def parse_into(text, out):
out[0] = int(text)
x = 0
parse_into("42", x)Output (last line of the traceback):
TypeError: 'int' object does not support item assignmentPython does not check the type of an argument when you make the call. The error appears inside the function, on the line that uses out. A statically typed language, such as Java, stops this kind of mistake before the program runs.
Practice
Exercise 1. Write def divide(a, b): that returns a tuple with the quotient and the remainder. Test it with 20 and 6, and then with 9 and 3.
Show the answer
The quotient for 20 and 6 is 3 and the remainder is 2, so the result is (3, 2). The quotient for 9 and 3 is 3 and the remainder is 0, so the result is (3, 0). The function body is return a // b, a % b.
Exercise 2. Write def min_max(values): for a list with one value, such as [7]. Check that the result has smallest 7 and largest 7. Use the MinMax dataclass from Example 1.
Show the answer
The function works without a special case. Both smallest and largest start at values[0], which is 7. The loop runs once, and no comparison changes either name. The result is MinMax(smallest=7, largest=7).
Exercise 3. A function def min_max_list(values): returns a list [smallest, largest]. Explain in one sentence why this is weaker than the dataclass version.
Show the answer
The reader must remember which position means which value, and Python does not check that, so if the two positions are swapped the program gives wrong results and no error appears.
Exercise 4. Write def first_even(xs): that returns the first even number in a list, or None if there is none. Test it with [3, 5, 8, 10] and with [1, 3].
Show the answer
Loop through the list. When x % 2 == 0, return x. After the loop, return None. The first list gives 8 and the second gives None. None is the right choice here because there is only one answer, and it may be missing.
Exercise 5. A function reads a list of words and must return the number of words and the longest word. Does this need a dataclass, a tuple, or None? Explain your choice in two sentences.
Show the answer
It needs a dataclass or a tuple, because there are two real answers that are always present: the count and the longest word. None can mark only one missing answer, so it cannot carry both. A dataclass with count and longest fields is clearer for a reader than a bare tuple.
Summary
- A Python function returns one value, and that value can be a tuple that holds several answers.
- A dataclass with named fields is the usual way to return two or three answers that need clear names.
- Python has no out parameter. A list holder works, but it hides the answer and can silently return a default value.
- Start a search for a smallest or largest value with the first element of the data, not with zero.
- Return
Nonefor a single answer that may be missing, and a tuple or a dataclass for several answers that are always present.