Functions and small tests: make a calculation reusable
Separate inputs, return values, and display, then verify a function with cases you can calculate by hand.
Learning lesson · Reviewed October 2, 2026. Allow additional time for the practice. Prerequisite: Conditions and loops. Python 3; no extra packages.
- Write a function with an explicit input and output.
- Distinguish returning a value from printing it.
- Use normal, boundary, and empty cases to check a calculation.
Repeating a calculation in several places creates several opportunities for it to drift. A function gives the calculation one name and one place to inspect. In this lesson, the task is to calculate the arithmetic mean of a list. The fictional observations are small enough that you can check every expected result by hand.
Write the contract before the function
Our contract is: accept a list of numbers, return its mean, and return None when the list is empty. Input validation is a separate task introduced later. The contract does not say that the function prints a report, rounds the answer, or edits the list. Keeping those decisions outside the calculation makes it easier to reuse.
def average(values):
"""Return the arithmetic mean, or None for no observations."""
if len(values) == 0:
return None
return sum(values) / len(values)
result = average([10, 20, 30])
print(result) # 20.0The name values is a parameter: inside a call, it refers to the supplied list. A return sends a result to the caller and ends that function call. The caller can display the result, compare it, or pass it to another function. print() displays text; displaying the correct number alone does not give the caller that number as a return value.
[10, 20, 30]→Calculation
60 / 3→Return
20.0→Display
print(result)
Test with answers that exist independently of the code
A useful expected answer comes from the contract and a hand calculation, not from copying what the function just printed. For [2, 4], the total is 6 and the count is 2, so the expected answer is 3. For one observation, the mean is that observation. For no observations, our chosen contract returns no numeric answer.
assert average([2, 4]) == 3.0
assert average([7]) == 7.0
assert average([]) is None
assert average([-2, 2]) == 0.0
print("4 checks passed")Each assert expresses a check; a false condition raises AssertionError in an ordinary Python run. These tiny checks help learning and debugging. Do not use assertions to validate untrusted input in a deployed application: Python can disable them in optimized execution. For production projects, use a test runner and explicit validation.
| Case | Expected result | What it can expose |
|---|---|---|
| Two observations | 3.0 for [2, 4] | Wrong total or denominator |
| One observation | 7.0 for [7] | Off-by-one denominator |
| Empty input | None | Division by zero or invented zero |
| Negative and positive | 0.0 for [-2, 2] | Incorrect handling of signs |
Keep precision and presentation separate
A displayed value such as 2.33 can be convenient while the underlying result contains more precision. Round at the presentation boundary unless your task explicitly requires intermediate rounding. Binary floating-point arithmetic can produce small representation differences, so exact equality is not appropriate for every numeric test. For a calculated non-integer result, a tolerance check can express the intended comparison.
from math import isclose
assert isclose(average([1, 2, 4]), 7 / 3)
print(f"Mean: {average([1, 2, 4]):.2f}") # Mean: 2.33Practice: count observations meeting a threshold
Write count_at_least(values, threshold). It should return a count, leave the list unchanged, and return zero for an empty list. Check [59, 60, 80] with a threshold of 60. Decide the expected answer before writing the implementation.
Show the worked solution
def count_at_least(values, threshold):
count = 0
for value in values:
if value >= threshold:
count += 1
return count
assert count_at_least([59, 60, 80], 60) == 2
assert count_at_least([], 60) == 0
assert count_at_least([60], 60) == 1This empty count is zero because it answers how many observations matched. An empty mean is different: there is no observed number to average.
Before moving on
Explain the contract in one sentence, identify what the function returns, and show a test that would fail if >= accidentally became >. The next lesson uses dictionaries to attach names to the fields that these functions process.
Official sources checked October 2, 2026. Examples, exercises, and rubrics are original Noetrion teaching material; fictional datasets are identified below.