Today's outcomes
- Distinguish parameters from arguments.
- Separate return values from display.
- Test a function without changing its inputs.
Before you start: Complete Day 06 and its practical challenge.
Understand the concept
A function gives a task one explicit interface
Yesterday's audit can process one list, but repeating its statements for another list duplicates the logic. A function defines the task once and accepts inputs through parameters. Calling it supplies arguments for a particular run. The interface should tell a reader what enters, what leaves, and what the function may change.
Write the contract before the body. Today's summary accepts a list containing nonnegative integer minutes or None. It returns the recorded count, accepted total, and average over recorded observations. None entries are missing and excluded from that average. With no recorded observations, the average is None. The function leaves the source list unchanged and performs no printing.
That contract deliberately narrows yesterday's audit: invalid negative values are outside today's accepted inputs. A reusable function does not become universally safe merely because it has a name. Day 17 will add boundary validation. Until then, document and test the assumptions.
Return sends a value; print sends text to a display
A return statement sends a result to the caller and ends the current function call. A function that reaches its end without returning a value returns None. Printing the correct mean does not give the caller that numeric mean. The caller might need to compare it, save it, or build a report.
Keep presentation outside a calculation when practical. A pure calculation is easier to test because its output can be compared directly with an expected value. It also avoids producing unwanted display lines when imported into another program. You can always add a reporting function later.
Parameters make dependencies visible
In summarize(minutes), minutes is the parameter name inside the function. In summarize([30, 60]), the supplied list is the argument. Local names such as total are created during the call and belong to that call's execution. Reusing a global total would make successive calls depend on hidden state.
Functions may accept defaults and keyword arguments. Use an immutable default such as a number or None when appropriate. A mutable list default is evaluated when the function is defined and can be shared across calls. Day 8 and Day 20 explore related shared-state mistakes; for now, pass each source list explicitly.
Worked example
def summarize(minutes):
"""Return recorded count, total, and mean; skip None observations."""
count = 0
total = 0
for value in minutes:
if value is None:
continue
count += 1
total += value
mean = None if count == 0 else total / count
return count, total, mean
result = summarize([30, None, 60, 0])
print(result)
assert result == (3, 90, 30.0)
assert summarize([]) == (0, 0, None)
assert summarize([None]) == (0, 0, None)
assert summarize([0]) == (1, 0, 0.0)
Returning several comma-separated values creates a tuple, which Day 9 examines. The conditional expression chooses the average without dividing by zero. Read it as “None if no recorded items exist, otherwise total divided by count.” It is an expression, not an if block with several statements.
| Call | Count | Total | Mean | Reason |
|---|---|---|---|---|
[30, None, 60, 0] |
3 | 90 | 30.0 | Zero is observed; None is missing |
[] |
0 | 0 | None | No observations |
[None] |
0 | 0 | None | No recorded observations |
[0] |
1 | 0 | 0.0 | A real zero-minute session |
The denominator is the recorded count, not the original list length. Dividing 90 by four would silently treat the missing observation as contributing to the average's population.
Guided practice
- Predict: what does a function return if it calls print but has no return statement? Build a tiny example and inspect the caller's result. Explain why the display and return value differ.
- Modify: add a threshold parameter to count qualifying sessions. Make the boundary inclusive. With
[59, 60, 80]and threshold 60, expect two matches; with an empty list, expect zero. - Reconstruct: rewrite summarize from its contract without opening the example. Check a normal list, a single observation, an empty list, and a list containing only missing observations.
An assert expresses a learning or test check that raises AssertionError when false in an ordinary run. Assertions can be disabled by optimized execution, so do not use them as the only validation of external input. Today they check your implementation against independent expected answers.
Daily challenge
Implement count_at_least(values, threshold=60). Accept a list of nonnegative integer minutes or None. Return the number of recorded values at least the threshold, ignore None, and leave the input unchanged. The function must return an integer and must not print its result internally.
| Values | Threshold | Expected count |
|---|---|---|
[59, 60, 80, None, 0] |
60 | 2 |
[59, 60, 80] |
80 | 1 |
[] |
60 | 0 |
[0] |
0 | 1 |
Use a copy of the original list to check nonmutation. Explain why the final case distinguishes an inclusive threshold from a strictly-greater comparison. Do not calculate expected values by calling the function you are trying to test.
Reference solution
Compare after attempting the challenge
def count_at_least(values, threshold=60):
count = 0
for value in values:
if value is not None and value >= threshold:
count += 1
return count
source = [59, 60, 80, None, 0]
snapshot = source.copy()
assert count_at_least(source) == 2
assert count_at_least([59, 60, 80], threshold=80) == 1
assert count_at_least([]) == 0
assert count_at_least([0], threshold=0) == 1
assert source == snapshot
print("Five contract checks passed")
The keyword call documents the chosen threshold. The missing-value check occurs before the comparison, and and short-circuits when the value is None. The copy operation is introduced in more detail tomorrow.
Debug and explain
A return placed inside the loop can end the function after only its first item. A count initialized outside the function can leak between calls. A print-only implementation can look correct while returning None. Test at least two calls in the same run so state leakage has a chance to appear.
Explain the contract in one sentence, identify the denominator in summarize, and give a case that fails if return is indented inside the loop. Then explain why a valid zero count is different from a missing average.
Review and next step
Save the function and its checks as one script. At your next review, write the contract first and reconstruct the function second. Day 8 will explain list copying, mutation, and the reason nonmutation is part of today's interface.
Sources and verification
Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.
Check your understanding
Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.
Review your practical work
Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.
Pass the knowledge check and finish the practical self-review to save completion.