Error Handling in Python
Effective error handling makes Python code robust and maintainable. Python uses exceptions for error flow, and the difference between fragile and solid code is usually how specifically you catch them and how much context you preserve when things go wrong.
The guiding principles: catch the specific exceptions you can handle (never a bare except), raise domain-specific exceptions that carry context, clean up reliably with context managers, and log failures with enough detail to debug them later.
TL;DR
- Catch specific exceptions, not a bare
except:. - Create custom exceptions for domain errors, carrying useful context.
- Use context managers (
with) for reliable cleanup. - Log errors with full context (
logger.exception).
Quick Example
Handle the errors you can, log and re-raise the ones you can't, and always clean up:
Core Concepts
The exception hierarchy
Catch Exception (not BaseException) for application errors, so you don't swallow KeyboardInterrupt/SystemExit.
Custom exceptions
Model your domain with an exception hierarchy that carries context:
Patterns
Context managers for cleanup
Logging with context
Result type (explicit, no exceptions)
Best Practices
- Catch specifically — handle the exceptions you understand; let the rest propagate.
- Don't silence errors —
except: passhides bugs; at minimum log them. - Use
finally/context managers so resources are always released. - Use
logger.exceptioninside anexceptblock to capture the traceback. - Raise custom exceptions with context for your domain's failure modes.
Common Mistakes
Catching everything and swallowing it
Losing the original error
FAQ
Why is a bare except: bad?
It catches everything, including KeyboardInterrupt and SystemExit, and hides bugs you'd want to see. Catch specific exceptions you can handle, and if you must catch broadly, use except Exception and log/re-raise rather than silently passing.
When should I create a custom exception?
When your domain has failure modes worth distinguishing — InsufficientStockError, PaymentDeclinedError. A small hierarchy (a base OrderError with specific subclasses) lets callers catch broadly or narrowly and lets you attach context (IDs, amounts) to the error.
What are context managers good for?
Guaranteeing cleanup — closing files/connections, committing or rolling back transactions, releasing locks — even when an exception occurs. The with statement runs the teardown reliably, which is cleaner and safer than scattering try/finally.
Exceptions or a result type?
Exceptions are Pythonic and right for genuinely exceptional conditions. A result type (Success/Failure) makes expected, recoverable errors explicit in the signature, so callers must handle them — useful at boundaries where you don't want failures to silently propagate. See Error Handling.
Related Topics
- Python — The language fundamentals
- Logging — Structured logging of errors
- Error Handling — Cross-language patterns
- API Design Best Practices — Mapping errors to responses
- Database Transactions — Rollback on error