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
def greet(name): """Return a friendly greeting.""" return f"Hello, {name}!" print(greet("Ana")) message = greet("Ben") print(message.upper())
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
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)
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
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))
5 0 15
Returning several values
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))
4 42 18.0
Python returns a tuple, which the caller unpacks.
Default values and keyword arguments
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"))
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:
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)
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
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])
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.