Python Functions: def, return and Returning Multiple Values

A Python def creates a function object and return hands back a value, or None. Returning several values as a tuple, the None trap and functions as values.

  • Course: Python study plan
  • Module: Functions
  • Kind: Lesson
  • Reading time: 12 min
  • Runtime: CPython 3.11

How do you define a function in Python?

Define a Python function with def, a name, parameters in parentheses and an indented body, as in def area(width, height): return width * height. The def statement creates a function object and binds the name when it runs; the body runs only when the function is called. return hands a value back, and a function without one returns None.

Lesson

A function in Python is an object created by a def statement, bound to a name like any other value, and called with parentheses. That one sentence explains most of what surprises people later: a function can be passed to another function, stored in a list, returned from a call, given attributes, and redefined. This lesson covers the statement, what return does and does not do, the None that every function returns when it says nothing, docstrings, and the discipline — one job, no hidden state, inputs in and a result out — that makes a function worth writing.

The statement

def area(width, height):
    """Return the area of a width × height rectangle."""
    return width * height

print(area(3, 4))      # 12

def runs when the interpreter reaches it: it compiles the body, creates a function object, and binds the name area to it. The body does not run until the function is called. That is why a function must be defined above the code that calls it at module level (a call at the top of a file to a function defined below it is a NameError), but functions can call each other in any order as long as every def has executed before the first call.

The parameters — width, height — are local names bound to the arguments when the function is called. Everything assigned inside the body is local too, created on entry and gone on return (Module 4 lesson 3).

return

return expr evaluates the expression, ends the call and hands the value back. A return with no expression, or falling off the end of the body, returns None. There is no way to return nothing at all — the caller always gets an object.

def find_index(xs, target):
    for i, x in enumerate(xs):
        if x == target:
            return i          # leaves the loop and the function at once
    return -1                 # the "not found" answer; without it the caller would get None

Several values are returned as one tuple, and unpacked at the call site:

def min_max(xs):
    return min(xs), max(xs)

lo, hi = min_max([3, 1, 4])

return inside a loop is the cleanest early exit (Module 3). return inside a try still runs the finally block (Module 10). And code after a return in the same block never runs — an unreachable line is a bug, not a comment.

The None trap

A function that computes something but forgets to return it hands back None, and the caller's next line fails with AttributeError: 'NoneType' object has no attribute … or TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'. The same trap hides in methods that work in place: xs.sort(), xs.append(x), d.update(...), random.shuffle(xs) all return None by design, so xs = xs.sort() throws the list away. The rule the standard library follows — a method that mutates returns None; a method that builds a new object returns it — is worth adopting in your own code.

Functions are objects

def double(x):
    return 2 * x

f = double                 # another name for the same function
print(f(21))               # 42
ops = [double, str, len]   # functions in a list
print(double.__name__)     # 'double'
print(type(double))        # <class 'function'>

Because functions are values, they can be arguments (sorted(words, key=len), map(double, xs)), return values (a function that builds and returns another — Module 4 lesson 3), and dictionary values (a dispatch table, Module 3). Calling is the only thing parentheses do: double is the function, double(3) is a call. Forgetting the parentheses passes the function where its result was wanted, and since a function object is truthy, if is_ready: is a silent bug.

Docstrings and help

The first statement of a body, if it is a string, is the docstring; help(area) and editors show it, and area.__doc__ holds it. Write it as what the function does for the caller, in the imperative: "Return the area…", not "This function computes…". A one-line docstring is enough for a small function; a longer one describes parameters, the return value and the exceptions raised (Module 15 covers the conventions). Type hints — def area(width: float, height: float) -> float: — are the other half of the contract and are covered in this module's last lesson.

What makes a function good

  • One job. A function that reads input, computes and prints does three, and none of them can be tested or reused alone. Read in main, compute in a function that takes values and returns values, print in main.
  • No hidden inputs. A function that reads a global has an input its signature does not show; pass it as a parameter.
  • No surprise outputs. A function that returns a value and also mutates its argument or prints is doing two things; pick one and say so in the name (sort versus sorted).
  • A name that says what it returns, for functions that return: is_prime, parse_line, best_score. A verb for functions that act: save, print_report.
def main():
    n = int(input())
    scores = [int(input()) for _ in range(n)]
    lo, hi = min_max(scores)
    print(f"{lo} {hi}")

That shape — main does I/O, helpers are pure — is how every reference solution in this track is written, and it is what makes the helpers testable in Module 18.

Pitfalls

  • Calling a function above its def at module level.
  • xs = xs.sort() and other in-place methods assigned.
  • A missing return on one path (the loop found nothing, the else branch was not written).
  • Passing f where f() was meant.
  • A helper that prints its result instead of returning it — the caller cannot use it.
  • A docstring that repeats the name instead of describing the contract.

Key takeaways

  • def creates a function object when executed and binds a name; the body runs only when called.
  • return ends the call with a value; no return means None; several values come back as a tuple.
  • In-place methods return None by convention; never assign their result.
  • Functions are values: pass them, store them, return them; parentheses call.
  • One job, explicit inputs, a returned result, a docstring stating the contract; I/O in main.

Common questions

How do I return multiple values from a Python function?

Return them separated by commas, which builds one tuple: return min(xs), max(xs). The caller unpacks it into several names, as in lo, hi = min_max(scores). The function still returns a single object; the tuple is what carries the values together.

Why does my Python function return None?

A function that ends without a return statement, or with a bare return, returns None. The usual cause is computing a value and never returning it, or a path — the loop found nothing, the else was not written — that falls off the end. In-place methods such as xs.sort() and xs.append(x) also return None by design.

What is the difference between return and print in Python?

return hands a value back to the caller, which can store it, test it or pass it on; print writes text to standard output, and the function still returns None. A helper that prints its result instead of returning it cannot be reused or tested, so compute in functions that return and print in main.

Are functions objects in Python?

Yes. A Python function is a first-class object: it can be bound to another name, stored in a list or dict, passed as an argument, as in sorted(words, key=len), and returned from another function. Parentheses are what call it — double is the function, double(3) is a call.

Exercises

Statistics helpers

Write three pure functions and a main that does all the input and output. min_max(xs) returns the smallest and largest values as a tuple; mean(xs) returns the average; spread(xs) returns max - min. main reads the numbers, unpacks the tuple, and prints the report. For an empty input print no data.

Input: one line of integers (possibly empty). Output: min <a> max <b> mean <m> spread <s> with the mean to two decimals, or no data.

3 1 4 1 5

prints

min 1 max 5 mean 2.80 spread 4

A pipeline of functions

Functions are values. The starter defines double, square, negate and increment. Read a line naming functions in order (possibly empty), build a list of function objects from a dictionary of names, then read a line of integers and pass each through the whole list in order.

Input: a line of function names, then a line of integers. Output: <x> -> <result> per integer.

double square
1 2 3

prints

1 -> 4
2 -> 16
3 -> 36

In this module: Functions

← Checkpoint — Control flow · Parameters and arguments — positional, keyword, defaults, *args and **kwargs →