3.4 — Breaking Code into Functions

One job per function, data in and data out.

Why This Matters

One long main program is hard to test and hard to change. A change request is much easier to add when each job has its own function.

The Idea

def add_item(inventory, name, qty, price):
    """Return the inventory with this item added, or updated if it's already there."""
    inventory[name] = {"qty": qty, "price": price}
    return inventory

def total_value(inventory):
    """Return the total value of everything in stock."""
    total = 0
    for details in inventory.values():
        total += details["qty"] * details["price"]
    return total

Notice what these functions don't do: they don't call input() or print(). They take data in, return data out, and leave the talking to the menu. That's what makes them easy to test.

Signs a function is doing too much: its name has “and” in it (like add_and_save), it runs longer than one screen, or you can't test it without typing things in.

Try It

  1. Pick one menu option from your project. Move its logic into a function with a docstring.
  2. Call the function on its own, with test data, and print the result.
  3. Write one test for it using the pattern from 3.3.

Practice Notebook

Work through more examples, in different contexts, in a Jupyter notebook. Every exercise has a check cell that tells you when your code is right.

Download the notebook (3.4)

How to open it: open the file in VS Code, pick a Python kernel, and run the cells from top to bottom with Shift + Enter.

Common Mistakes

  • Printing instead of returning. If the function prints, the caller gets None and can't use the result.
  • Forgetting return. Same problem: the caller gets None.
  • Changing a variable from outside the function instead of passing it in and returning it.

You're Done With This Lesson When…

Each function does one job, returns its result, and works when you call it without the menu.