Python Error Handling
debt(d3/e2/b5/t5)
Closest to 'default linter catches the common case' (d3) — pylint, ruff (E722), and flake8 flag bare except: directly; broad Exception catching also flagged by default rules.
Closest to 'one-line patch' (e1), nudged to e2 — per quick_fix, replacing 'except:' with 'except SpecificError as e:' plus logging is typically a one- or two-line change per occurrence, though multiple sites may need updating.
Closest to 'persistent productivity tax' (b5) — error handling style applies across web and CLI contexts (per applies_to); sloppy patterns silently swallow bugs throughout the codebase, creating ongoing debugging tax across many work streams.
Closest to 'notable trap most devs eventually learn' (t5) — per misconception, the belief that broad except is safe is canonical; bare except swallowing KeyboardInterrupt/SystemExit is a documented gotcha that competent devs guess wrong about initially.
Also Known As
TL;DR
Explanation
Python exceptions are objects in a hierarchy (BaseException > Exception > specific types). Best practices: catch the most specific exception possible, not bare except or except Exception. The else clause runs when no exception was raised — useful for code that should only run on success. finally always runs — use it for cleanup. Custom exceptions should inherit from Exception, not BaseException. Use raise from to preserve exception chains.
Common Misconception
Why It Matters
Common Mistakes
- Bare except: clause — catches everything including SystemExit and KeyboardInterrupt; always specify the exception type.
- Using pass in an except block — silently swallows the error with no logging or handling.
- Not using raise from to preserve the original exception context: raise NewException(...) from original_exc.
- Catching broad Exception when a specific type is known — makes error messages less informative and hides unrelated bugs.
Code Examples
# Silent exception swallowing — hides all errors:
def get_user(user_id):
try:
return db.fetch(user_id)
except: # Catches EVERYTHING including KeyboardInterrupt
pass # Returns None silently — caller has no idea what went wrong
# Specific exception handling with context:
class UserNotFoundError(ValueError):
def __init__(self, user_id):
super().__init__(f'User {user_id} not found')
self.user_id = user_id
def get_user(user_id: int) -> User:
try:
return db.fetch(user_id)
except DatabaseConnectionError as e:
raise RuntimeError('Database unavailable') from e # Preserve chain
except RecordNotFoundError:
raise UserNotFoundError(user_id) # Domain-specific exception
# No finally needed here — let other exceptions propagate