Functions and Contracts
What this concept is
A function groups a computation behind a name. Parameters receive arguments at a call; return sends a result back. A contract states acceptable inputs, the result, and failure behavior. Local variables belong to a particular call.
Why it matters here
Separating calculation from printing lets us test the tracker without typing into an interactive menu.
Concrete example
def total_minutes(sessions):
total = 0
for minutes in sessions:
total += minutes
return total
answer = total_minutes([25, 15])
assert answer == 40
assert total_minutes([]) == 0
print(answer)
def creates the function; its body runs when called. return ends the call. assert reports a failed expectation when its condition is false. The assertions here are development checks, not input-validation rules.
Follow a call and its return
In total_minutes([25, 15]), [25, 15] is the argument, the actual supplied value. sessions is the parameter, the local name used inside the function. The call starts a new local total at 0, visits both values, and returns 40. Only then does answer = ... finish by binding answer to 40.
Calling it again with [10] starts another call with a new local total; it does not continue from 40. Names created inside a call are not automatically available outside it. This keeps a function's temporary work separate from the caller's names. A collection passed as an argument can still be shared; Lesson 4 explains why changing that collection is different from assigning a local number.
Print and return solve different problems
def displayed_total(sessions):
print(sum(sessions))
def calculated_total(sessions):
return sum(sessions)
shown = displayed_total([10, 20])
stored = calculated_total([10, 20])
assert shown is None
assert stored == 30
print(stored + 5)
sum is a built-in total operation; the earlier loop shows the steps it replaces. The first call displays 30, but assigns None to shown because it has no return statement. The second call returns 30 without displaying it; the final print displays 35. The caller can use a returned number in another calculation. It cannot recover a return value merely because a function printed a number.
Make the promise explicit
A useful contract for our first total_minutes is: “Given a list of already validated integer durations, return their sum, return 0 for an empty list, and leave the input unchanged.” The function trusts its caller to validate. It is not dishonest to make this assumption explicit; it would be misleading to claim the function itself rejects every bad value.
Write the contract as a docstring immediately inside def when implementing your own function:
def minutes_after_break(minutes, break_minutes):
"""Return remaining minutes; require 0 <= break_minutes <= minutes."""
return minutes - break_minutes
assert minutes_after_break(25, 5) == 20
assert minutes_after_break(25, 25) == 0
This docstring documents a requirement; it does not enforce it. Calling with a larger break would produce a negative result unless you add validation. Decide where validation belongs before adding checks everywhere.
Decompose one small responsibility at a time
The tracker separates reading text, parsing it, calculating a summary, saving and displaying. A calculation should not unexpectedly request keyboard input, print a menu or modify a file. Those observable actions are side effects. Keeping calculation separate makes its examples easy to test. You do not need classes, decorators or recursion for this module: first get comfortable passing values into functions and using their results.
Common confusion / misconception
Printing a result does not return it. A function with no explicit return produces None. Returning inside the loop would stop after the first session. Calling a function and defining it are separate actions.
How to use it
Before coding, write two examples and one edge case. Choose a function that can return a value without reading a keyboard or file. Keep input conversion and output formatting outside this computation.
Check yourself
What should total_minutes([10, 10]) return, and why is a duplicate retained? Answer: 20; equal durations can describe distinct sessions.
Mini drill or application
Implement count_at_least(sessions, target) with an inclusive comparison. Test below, equal to and above the target. State whether validation belongs to this function or its caller.
Read this only if stuck
Official source: Python tutorial. Read only the matching section after attempting the example. These lessons and exercises are original curriculum material.