Exceptions and Validation
Define validation boundaries, raise precise exceptions, catch only failures you can handle, and preserve useful context instead of hiding defects.
Before this lesson
Choose exception types deliberately
Write narrow recovery handlers
Preserve exception context
The short answer
Validate data where it enters the system, raise an exception when a function cannot honor its contract, and catch it only where the program can recover or present useful context. Keep try blocks narrow.
Build the mental model
Exceptions separate the successful return path from an inability to fulfill the contract. ValueError fits a value with an unacceptable form or range; TypeError fits an unsupported kind of object. Domain-specific exceptions can communicate higher-level failures.
The central idea in this lesson is failure contracts. Read the rule, predict a concrete outcome, and then run the smallest example that can confirm or reject the prediction. This separates knowledge from familiarity with syntax.
Make the design choice explicit
Catch the most specific exception at a boundary such as a command loop or HTTP handler. Use raise ... from error to preserve a lower-level cause while adding domain context. A finally block is for unconditional cleanup, though context managers are usually clearer.
def parse_quantity(text):
try:
quantity = int(text)
except ValueError as error:
raise ValueError("quantity must be a whole number") from error
if quantity <= 0:
raise ValueError("quantity must be positive")
return quantity
print(parse_quantity("4"))Expected output
4
Trace the example before running it. Identify each input, transformation, returned value, and side effect. Then change one boundary value and explain why the new behavior follows from the rule rather than memorizing the output.
Recognize failure modes
Never use bare except to make a failure disappear. Do not use exceptions for normal membership checks when a direct condition is clearer. Error messages should state the invalid value category and expected rule without exposing secrets.
Use a professional practice loop
Turn the concept into a repeatable workflow: write down the expected behavior, implement one coherent change, run a representative example, and retain a regression check. If the result surprises you, capture the exact input and error before changing anything.
Review the program for names, boundaries, and hidden side effects. A solution is complete when another developer can understand its contract, reproduce its setup, and verify both the successful path and one meaningful failure path.
Quick knowledge check
Answer before you reveal.
01Why should a try block be narrow?
It ensures the handler catches only the operation it understands and does not mask unrelated defects.
02What four steps make the practice loop reliable?
Predict the behavior, make one coherent change, run it, and retain a check that detects regression.
Exercise
Practice challenge
Create a parser for a date and positive duration, translate low-level conversion failures into clear validation messages, and test every boundary.
Requirements
- The successful path produces the documented result
- At least one boundary or invalid case is handled deliberately
- Calculation or domain logic is separated from console interaction
Optional extension: Add one automated regression check for the most important rule.
Open in Python compilerLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
Is failure contracts only important in large programs?
No. Small programs reveal the same rules with less noise, and learning the rule early prevents fragile habits from becoming architecture.
Should I memorize every API used here?
No. Memorize the mental model and how to verify behavior. Use documentation for exact names and parameters when needed.
How do I know the exercise is finished?
Meet every success criterion, test at least one boundary or failure case, and explain why the output follows from the code.