Functions

A function packages a piece of logic under a name so you can run it whenever you need, with different inputs each time. Functions are how programs are broken into understandable, testable, reusable parts.

Defining and calling

PY define.py
def greet(name):
    """Return a friendly greeting."""
    return f"Hello, {name}!"

print(greet("Ana"))
message = greet("Ben")
print(message.upper())
Output
Hello, Ana!
HELLO, BEN!

def starts the definition, followed by the function's name, its parameters in parentheses and a colon. The indented body runs each time the function is called with arguments. The optional docstring documents what it does.

return

PY return.py
def area(width, height):
    return width * height

def describe(width, height):
    print(f"A {width}×{height} rectangle")

a = area(3, 4)
b = describe(3, 4)
print(a, b)
Output
A 3×4 rectangle
12 None

return hands a value back to the caller and ends the function. A function without return returns None. Prefer returning results over printing them — returned values can be stored, combined, tested and displayed however the caller likes.

Returning early

PY early.py
def shipping_cost(total, express=False):
    if total <= 0:
        return 0
    if total >= 50:
        return 0
    return 15 if express else 5

print(shipping_cost(20), shipping_cost(80), shipping_cost(20, express=True))
Output
5 0 15

Returning several values

PY multiple.py
def stats(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

low, high, mean = stats([4, 8, 15, 16, 23, 42])
print(low, high, round(mean, 2))
Output
4 42 18.0

Python returns a tuple, which the caller unpacks.

Default values and keyword arguments

PY defaults.py
def make_coffee(size="medium", milk=False, sugar=0):
    extras = []
    if milk:
        extras.append("milk")
    if sugar:
        extras.append(f"{sugar} sugar")
    return f"{size} coffee" + (f" with {', '.join(extras)}" if extras else "")

print(make_coffee())
print(make_coffee("large", True))
print(make_coffee(sugar=2))
print(make_coffee(milk=True, size="small"))
Output
medium coffee
large coffee with milk
medium coffee with 2 sugar
small coffee with milk

Parameters with defaults can be left out. Passing arguments by name (sugar=2) lets you skip earlier ones and makes calls self-explanatory. Lesson 29 covers argument passing in depth.

Functions are objects

A function is a value like any other: you can assign it to a variable, store it in a list or dictionary, and pass it to another function:

PY objects.py
def double(n):
    return n * 2

def square(n):
    return n * n

operations = {"double": double, "square": square}

for name, fn in operations.items():
    print(name, fn(7))

def apply_twice(fn, value):
    return fn(fn(value))

print(apply_twice(square, 3))
print(double)
Output (the address varies)
double 14
square 49
81
<function double at 0x10a3c2f20>

Note the difference between double (the function itself) and double(7) (calling it). The memory address shown when printing a function changes every run.

Docstrings and help

PY docstrings.py
def convert(celsius):
    """Convert a temperature from Celsius to Fahrenheit.

    >>> convert(100)
    212.0
    """
    return celsius * 9 / 5 + 32

print(convert.__name__)
print(convert.__doc__.splitlines()[0])
Output
convert
Convert a temperature from Celsius to Fahrenheit.

Write a one-line summary first, then details if needed. help(convert) in the shell and editor tooltips display it. The >>> example can even be run as a test (lesson 53).

Designing good functions

  • Name them with verbs: calculate_total, send_email, is_valid.
  • One job each. If you describe a function with "and", consider splitting it.
  • Prefer inputs and outputs over reading and changing global variables.
  • Keep the parameter list short. Many related parameters may belong together in a dictionary or dataclass.

Try it yourself

Try it yourself
Console output