064. Defining Functions
Name and reuse blocks of logic
064. Defining Functions
Aryan has been copy-pasting his alert-level logic in three different places:
# In the display loop
if proc["rss_mb"] > 1000:
level = "CRITICAL"
elif proc["rss_mb"] > 500:
level = "WARNING"
else:
level = "OK"
# In the log writer (same code again)
if proc["rss_mb"] > 1000:
level = "CRITICAL"
...A senior dev tells him: extract it into a named function once.
def alert_level(rss_mb):
if rss_mb > 1000:
return "CRITICAL"
elif rss_mb > 500:
return "WARNING"
return "OK"
# Now every callsite is one line
level = alert_level(proc["rss_mb"])Aryan’s first version forgets return and spends ten minutes wondering why alert_level(1200) returns None. He learns: Python functions return None implicitly unless you explicitly use return. Always return explicitly when a value is expected.
💡 Fun fact: The principle “a function should do one thing” was formalized by Edsger Dijkstra in the 1970s and later popularized as the Single Responsibility Principle by Robert C. Martin. Research by Microsoft and NASA has shown that functions longer than ~20 lines have disproportionately higher defect rates — Aryan’s alert_level function, which does exactly one thing and fits in 5 lines, is a textbook example of the right approach.
⚠️ Watch out: Missing return is one of the most common Python bugs — n ** 3 inside a function computes the value but immediately discards it, returning None implicitly. The error is silent: the function runs without raising an exception, but any code that uses the return value will get None instead of a number, usually causing a TypeError somewhere else in the program.
🤔 Think about it: Functions in Python are first-class objects — you can store them in variables, pass them to other functions, and return them from functions. sorted(data, key=square) passes the square function itself as an argument. How does this enable patterns like sorting by a computed property, and what problem would you have to solve manually in C that key= in sorted() handles for you?
Learning objectives
- Define functions with def and parameters
- Use return to send values back to the caller
- Write focused functions that do one thing
Key concepts
- def
- function definition
- return
Try it
Concept detail
def name(parameters): defines a function. Call it with name(arguments).
The return trap: def cube(n): n ** 3 # computes the value, then DISCARDS it — returns None def cube(n): return n ** 3 # sends the value back to the caller
A function without return (or with bare return) returns None. This is one of the most common beginner bugs — the function runs without error but produces None when you try to use the result.
Functions are first-class objects in Python: stored in variables: f = square passed as arguments: sorted(data, key=square) returned from funcs: def make_adder(n): return lambda x: x + n
Single Responsibility Principle: each function should do ONE thing. Functions that do “one thing well” are easier to test, reuse, and debug.
Docstrings document intent: def alert_level(rss_mb): “”“Return ‘CRITICAL’, ‘WARNING’, or ‘OK’ based on RSS usage.”“” …
Solution
import math
def square(n):
return n * n
def cube(n):
return n ** 3
def hypotenuse(a, b):
return math.sqrt(a * a + b * b)
def is_divisible(n, divisor):
return n % divisor == 0Tests
def test_square():
assert square(4) == 16
assert square(-3) == 9
assert square(0) == 0
def test_cube():
assert cube(3) == 27
assert cube(2) == 8
def test_cube_not_none():
assert cube(3) is not None # catches missing return
def test_hypotenuse():
assert abs(hypotenuse(3, 4) - 5.0) < 0.001
def test_hypotenuse_not_none():
assert hypotenuse(3, 4) is not None # catches missing return
def test_is_divisible_true():
assert is_divisible(10, 2) == True
assert is_divisible(9, 3) == True
def test_is_divisible_false():
assert is_divisible(7, 2) == False