Skip to content
Lesson 9 of 9

Step 1 of 4 · Reading · ~4 min

Learn

Real-World Python

Putting It All Together

You've learned classes, dunder methods, generators, decorators, regex, and JSON as separate tools. Real programs are where they meet. The most common shape for a real program is the command loop: read a command, parse it, dispatch to the right handler, mutate some state, print a result. That's what a shell is, what a database CLI is, and what this lesson's inventory manager is.

The architecture

Three layers, cleanly separated:

  1. State + rules — a class that owns the data and enforces the invariants (chapter 1).
  2. Dispatch — mapping command names to handlers instead of growing an if/elif ladder forever.
  3. The I/O loop — reading stdin until EOF and printing exact output.

Here is the shape on a different problem — seat bookings for a small theatre — so you can see the skeleton without seeing your own answer:

python

Look closely at book and cancel: each checks its failure case before touching the data, and returns immediately when it fires. Validate first, mutate last — a rejected command must leave the state exactly as it was. Guarding invariants like this is the whole reason state lives inside a class instead of a bare dict scattered through the script.

Two dictionary habits carry straight into your own handlers. self.held.get(name, 0) reads a possibly-absent key without a KeyError, which is how a lookup for something never added still answers 0. And sorted(self.held) iterates a dict's keys in sorted order — a dict is not sorted on your behalf, so any alphabetical listing has to ask for it.

Reading commands until EOF

python

for line in sys.stdin is the EOF-safe idiom — the loop simply ends when input ends. A while True: input() loop raises EOFError at the end and needs a try/except to avoid crashing after the last command. The if not parts: continue guard matters too: a trailing blank line splits to an empty list, and parts[0] on it is an IndexError that ends your program mid-run.

Note int(parts[2]). Everything split() hands you is text, so a count that is going to be compared or subtracted has to be converted first; '5' < 3 is a TypeError, not a comparison.

If the if/elif ladder bothers you, dispatch through a dict — functions are first-class values:

python

Exact output is the contract

Graders — and every real program that consumes your output — compare text character by character. Our grader forgives exactly one thing: whitespace at the very end of your output. Nothing else. error: not found is not Error: Not Found. apple: 5 has a colon and a space. An empty inventory prints the single word empty, not nothing. When output is a protocol, formatting is correctness.

Where do the other tools fit? A production version of this program would persist the inventory dict with json.dump on exit and json.load on startup, validate item names with a regex, and wrap each handler in a logging decorator. The architecture wouldn't change — that is the payoff of separating state, dispatch, and I/O.

Your exercise

Implement add, remove, search, and list exactly as specified. The mistakes the grader will catch: (1) remove on an item that was never added must print error: not found — the visible test issues a remove before any add, so unguarded self.items[name] access crashes; (2) a remove larger than the stock must print error: insufficient and leave the count unchanged — the hidden test runs add apple 3, remove apple 5, then expects search apple to print 3; (3) list on an empty inventory must print empty; (4) list lines are alphabetically sorted and formatted name: quantity with a colon and a space.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…