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

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

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

References