Key Takeaways: Error Handling
One-Sentence Summary
Python's exception system lets you write programs that handle errors gracefully instead of crashing — and the EAFP philosophy ("Easier to Ask Forgiveness than Permission") means you should try operations and handle failures, rather than checking every precondition upfront.
Three Types of Errors
| Type | When Caught | Example | How to Fix |
|---|---|---|---|
| Syntax error | Before running (parsing) | print("hello" |
Fix the syntax |
| Runtime error (exception) | During execution | int("abc") |
Handle with try/except |
| Logic error | Never (produces wrong output) | Using - 32 instead of + 32 |
Test and debug (Chapter 13) |
The try/except/else/finally Pattern
try:
# Code that might fail
except SpecificError:
# Handle that specific error
else:
# Runs ONLY if try succeeded (no exception)
finally:
# ALWAYS runs — cleanup goes here
| Clause | When It Runs | Purpose |
|---|---|---|
try |
Always (first) | Attempt the risky operation |
except |
Only if a matching exception occurs | Handle the error |
else |
Only if NO exception occurs | Success-only code (keep try short) |
finally |
ALWAYS, no matter what | Resource cleanup (close files, etc.) |
An except clause also catches every subclass of the exception it names (except OSError catches FileNotFoundError and PermissionError), and Python uses the first clause that matches, so list specific exceptions before general ones.
Raising Exceptions
def validate_score(score):
if score < 0 or score > 100:
raise ValueError(f"Score must be 0-100, got {score}")
return score
Guidelines:
- Use the most specific exception type (ValueError, not Exception)
- Include the actual problematic value in the message
- Raise early (in the function), catch late (in the caller)
- When translating one exception into another, use raise NewError(...) from e so the traceback shows the original cause
LBYL vs EAFP (Threshold Concept)
| LBYL ("Look Before You Leap") | EAFP ("Easier to Ask Forgiveness") |
|---|---|
| Check conditions first, then act | Try the operation, handle failure |
if key in d: val = d[key] |
try: val = d[key] except KeyError: ... |
| Extra checks clutter happy path | Happy path is clear |
| Race condition risk (check-then-act gap) | No race condition |
| Better when failures are common, or would only surface after expensive work | Better for most Python code |
Python's default: EAFP. Use LBYL only when you have a specific reason.
Custom Exceptions
class TaskFlowError(Exception):
"""Base exception for TaskFlow."""
pass
class TaskNotFoundError(TaskFlowError):
pass
Custom exceptions add clarity (the name tells you what went wrong) and enable selective catching.
Anti-Patterns to Avoid
| Anti-Pattern | Why It's Bad | What to Do Instead |
|---|---|---|
Bare except: |
Catches everything, including Ctrl+C | except SpecificError: |
except Exception: |
Hides bugs from unrelated code | Catch specific types |
except: pass |
Silently loses errors | Log or report the error |
Huge try blocks |
Can't tell which line caused the exception | Keep try blocks short |
Exceptions instead of a plain loop or if |
Confusing, and can hide unrelated errors | Use for/if when they say it directly |
Key Patterns from This Chapter
- Retry loop:
while True:→try:convert andreturnthe value →except ValueError:print "try again" and loop - Graceful degradation: If a file is corrupt, start fresh instead of crashing
- Raise early, catch late: Functions validate and raise; callers decide how to respond
- Resource cleanup with finally: Guarantee files/connections are closed
Reading Tracebacks
Read from the bottom up: 1. Last line = exception type and message (the "what") 2. Lines above = the call chain that led to the error (the "where") 3. Top line = where the program started the failing chain
What's Next
Chapter 12: Organize code into modules and packages — split TaskFlow into separate importable files. Chapter 13: Testing — systematically verify your error handling works.