Python's contextlib.suppress is the cleanest way to handle expected exceptions
I had a codebase full of try/except/pass blocks for expected exceptions — deleting a key that might not exist, reading a file that might be missing, removing a set item that might not be there. Then I found contextlib.suppress and replaced all of them with a cleaner, more readable pattern. It signals intent clearly: "this exception is expected and acceptable here."
The basic pattern
from contextlib import suppress
# Before: verbose and buries the intent
try:
del cache[key]
except KeyError:
pass
# After: explicit and readable
with suppress(KeyError):
del cache[key]
# Multiple exceptions
with suppress(FileNotFoundError, PermissionError):
os.remove(temp_file)
# Works for any expected exception
with suppress(ValueError):
value = int(user_input) # Silently ignore if not a valid int
Real-world use cases
import os
import json
from contextlib import suppress
from pathlib import Path
# Cleanup operations
def cleanup_temp_files(paths: list[str]) -> None:
for path in paths:
with suppress(FileNotFoundError, PermissionError):
os.remove(path)
# Dictionary operations
def remove_keys(d: dict, keys: list[str]) -> None:
for key in keys:
with suppress(KeyError):
del d[key]
# Set operations
def safe_discard_all(s: set, items: list) -> None:
for item in items:
with suppress(KeyError):
s.remove(item) # discard() is better here, but just as example
# Config loading with fallback
def load_config(path: str) -> dict:
config = {}
with suppress(FileNotFoundError, json.JSONDecodeError):
with open(path) as f:
config = json.load(f)
return config # Returns empty dict if file missing or invalid JSON
# Registry cleanup
def unregister_handlers(registry: dict, handler_names: list[str]) -> None:
for name in handler_names:
with suppress(KeyError):
del registry[name]
When NOT to use suppress
## WRONG: suppressing exceptions to hide bugs
with suppress(AttributeError):
user.process_payment() # AttributeError might mean user is None — a bug!
# RIGHT: only suppress truly expected exceptions
if user is not None:
user.process_payment()
## WRONG: suppressing broad exceptions
with suppress(Exception): # Way too broad — hides everything
important_operation()
# RIGHT: be specific about which exceptions are acceptable
with suppress(ConnectionTimeout):
cache.warm_up()
Other contextlib gems
from contextlib import contextmanager, asynccontextmanager, ExitStack
# @contextmanager: turn a generator into a context manager
@contextmanager
def timer(name: str):
import time
start = time.perf_counter()
try:
yield
finally:
elapsed = time.perf_counter() - start
print(f"{name}: {elapsed:.3f}s")
with timer("database query"):
results = db.execute_long_query()
# @asynccontextmanager: async version
@asynccontextmanager
async def managed_connection(pool):
conn = await pool.acquire()
try:
yield conn
finally:
await pool.release(conn)
async with managed_connection(pool) as conn:
await conn.execute("SELECT 1")
# ExitStack: dynamically stack context managers
def process_files(file_paths: list[str]) -> None:
with ExitStack() as stack:
files = [
stack.enter_context(open(path))
for path in file_paths
]
# All files opened, all closed when block exits (even on error)
for file in files:
process(file.read())
suppress vs EAFP vs LBYL
Python style favors EAFP (Easier to Ask Forgiveness than Permission) over LBYL (Look Before You Leap):
## LBYL (check before doing) — more verbose, can race
if key in cache:
value = cache[key]
## EAFP (try and handle) — Pythonic
try:
value = cache[key]
except KeyError:
value = default
## EAFP with suppress (when you don't need the value)
with suppress(KeyError):
cache.invalidate(key)
suppress is the clearest possible signal that an exception is expected and acceptable. It is self-documenting: you can read "with suppress(FileNotFoundError)" and immediately understand that the file may not exist and that is fine. The bare try/except/pass requires reading the exception type AND inferring the intent. Use suppress whenever the intent is "this may fail and that is acceptable" — not as a catch-all for unknown failures.