Build a small program from functions
A real program reads data, checks it, computes something and reports it. Giving each step its own function makes the program easy to test, change and read from the top down.
- Plan a program as steps: validate, compute, report
- Write one function per step and connect them with return values
- Keep the input data unchanged while producing a new result
Every program in this course so far has had the same skeleton, even when it wasn't written down: some data comes in, bad data is caught, something is computed, and a result goes out. Writing each of those steps as its own function makes the skeleton visible. The bottom of the program then reads like a plan, and each function can be checked on its own with a couple of inputs.
Run each example to see what it prints. Running examples doesn't count toward progress.
def valid_score(score): """Return True for a whole-number score from 0 to 100.""" return score is not None and 0 <= score <= 100 def letter(score): """Return the letter grade for a valid score.""" if score >= 90: return "A" if score >= 80: return "B" if score >= 70: return "C" if score >= 60: return "D" return "F" def grade_all(scores): """Return (name, letter) for valid scores and the names with invalid ones.""" graded = [] invalid = [] for name, score in scores: if valid_score(score): graded.append((name, letter(score))) else: invalid.append(name) return graded, invalid def report(graded, invalid): for name, grade in graded: print(f"{name:<6}{grade}") if invalid: print("check:", invalid) scores = [("Ana", 92), ("Ben", 58), ("Chen", 104), ("Dara", 71), ("Eli", None)] graded, invalid = grade_all(scores) report(graded, invalid)
The second program is a restock report. Its steps are: apply the day's sales to a copy of the stock (the original is the morning's record and must not change), find what has fallen below its reorder level, and work out how many packs to order. A while loop (see Loops & Iteration) rounds up to whole packs.
def apply_sales(stock, sales): """Return a new stock dictionary after the sales; unknown products are ignored.""" after = stock.copy() for product_id, units in sales: if product_id in after: after[product_id] = after[product_id] - units return after def packs_needed(units, level, pack_size): """Return how many packs bring units back up to at least level.""" packs = 0 while units + packs * pack_size < level: packs = packs + 1 return packs stock = {"P0607": 30, "P0208": 8, "P0659": 12} levels = {"P0607": 20, "P0208": 10, "P0659": 5} sales = [("P0607", 14), ("P0208", 3), ("P0999", 1), ("P0659", 2)] after = apply_sales(stock, sales) for product_id, units in after.items(): packs = packs_needed(units, levels[product_id], 6) if packs > 0: print(f"{product_id}: {units} left, order {packs} packs") print(stock["P0607"], after["P0607"])
When to use it: for any program longer than a screen. Name the steps first, then write a function for each. Mistakes to avoid:
- One function that validates, computes and prints at once: it can't be reused or checked.
- Changing input data inside a step when the caller still needs the original: copy first.
- Steps that communicate through outer names instead of parameters and return values.
- Plan the program as steps (validate, compute, report) and write one function per step.
- Connect steps with parameters and return values; only the report prints.
- Copy input data before changing it when the caller still needs the original.
- Next module: string algorithms: cleaning, splitting, searching, counting and validating text.