Python Exception Handling Cheat Sheet
Python error handling covering try/except/else/finally, catching multiple exceptions, raising and chaining errors, and custom exceptions.
try / except / else / finally
The full structure of Python's exception handling block.
try: result = 10 / 0except ZeroDivisionError as e: print(f"Error: {e}")else: print("No error occurred") # runs only if try succeededfinally: print("Always runs") # runs no matter what
Catching Multiple Exceptions
Handling several exception types with one or more except clauses.
try: value = int(input("Enter a number: "))except (ValueError, TypeError) as e: print(f"Invalid input: {e}")except Exception as e: print(f"Unexpected error: {e}") # broad fallback, keep specific
Raising & Chaining Exceptions
Raising your own errors and preserving the original cause.
def validate_age(age): if age < 0: raise ValueError("Age cannot be negative") return agetry: validate_age(-5)except ValueError as e: raise RuntimeError("Validation failed") from e # preserves e as __cause__
Custom Exception Classes
Defining domain-specific exceptions by subclassing Exception.
class InsufficientFundsError(Exception): def __init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__(f"Cannot withdraw {amount}, balance is {balance}")def withdraw(balance, amount): if amount > balance: raise InsufficientFundsError(balance, amount) return balance - amount
Common Built-in Exceptions
Frequently encountered exception types in the standard library.
- ValueError- correct type but inappropriate value, e.g. int('abc')
- TypeError- operation applied to an object of an inappropriate type
- KeyError- dictionary key not found
- IndexError- sequence index out of range
- AttributeError- attribute reference or assignment fails
- FileNotFoundError- file or directory does not exist
- ZeroDivisionError- division or modulo by zero
- StopIteration- raised by next() when an iterator is exhausted
Exception Groups & except*
Python 3.11+ lets you raise and selectively handle multiple unrelated exceptions at once.
def run_tasks(): errors = [] for task in [lambda: 1/0, lambda: int("x")]: try: task() except Exception as e: errors.append(e) if errors: raise ExceptionGroup("task failures", errors)try: run_tasks()except* ZeroDivisionError as eg: print(f"Math errors: {eg.exceptions}")except* ValueError as eg: print(f"Value errors: {eg.exceptions}")
contextlib.suppress & Custom __exit__
Suppressing specific exceptions cleanly, and how context managers can swallow errors themselves.
from contextlib import suppresswith suppress(FileNotFoundError): open("missing.txt").read() # no try/except boilerplate neededclass IgnoreErrors: def __init__(self, *exc_types): self.exc_types = exc_types def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): return exc_type is not None and issubclass(exc_type, self.exc_types) # returning True from __exit__ suppresses the exceptionwith IgnoreErrors(KeyError, IndexError): [][0] # swallowed
Inspecting Tracebacks Programmatically
Extracting structured traceback data instead of just printing it.
import traceback, systry: 1 / 0except ZeroDivisionError: exc_type, exc_value, exc_tb = sys.exc_info() formatted = traceback.format_exc() # full string, same as print_exc frames = traceback.extract_tb(exc_tb) # list of FrameSummary for frame in frames: print(f"{frame.filename}:{frame.lineno} in {frame.name}") print(exc_value.__traceback__.tb_lineno) # traceback attached to the exception object
Manual Retry Decorator with Exponential Backoff
A real-world pattern for retrying flaky operations while preserving the original exception chain.
import time, functoolsdef retry(exceptions=(Exception,), tries=3, delay=0.5, backoff=2): def decorator(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): _delay = delay last_exc = None for attempt in range(1, tries + 1): try: return fn(*args, **kwargs) except exceptions as e: last_exc = e if attempt == tries: raise RuntimeError(f"failed after {tries} attempts") from e time.sleep(_delay) _delay *= backoff return wrapper return decorator@retry(exceptions=(ConnectionError,), tries=4)def fetch(): ...
Advanced Gotchas
Subtle behaviors that trip up even experienced Python developers.
- return in finally- a `return`/`break`/`continue` in `finally` silently discards any exception in flight
- except order matters- a broad `except Exception` before a specific `except ValueError` makes the specific clause unreachable
- __cause__ vs __context__- `raise X from Y` sets __cause__ explicitly; an exception raised inside an except block auto-sets __context__ for implicit chaining
- raise from None- suppresses the chained traceback display entirely, useful for hiding internal implementation errors from callers
- BaseException vs Exception- SystemExit and KeyboardInterrupt inherit from BaseException, not Exception, so `except Exception` never catches them
- exception objects hold frames- a caught exception keeps a reference to its traceback (and thus local variables) alive until it goes out of scope, which can leak memory in long-lived loops
Never write a bare `except:` clause — it catches everything including KeyboardInterrupt and SystemExit, making your program hard to stop and hiding real bugs. Catch specific exception types, or at most `except Exception:`.