Most Python developers know with open(...). Fewer use context managers for their own resources, and that is where the real cleanup wins hide. Here are five patterns.
1. Write Your Own with contextlib
You do not need a full class. @contextmanager turns a generator into a context manager.
from contextlib import contextmanager @contextmanager def timer(label): import time start = time.perf_counter() try: yield finally: print(f"{label}: {time.perf_counter() - start:.3f}s") with timer("query"): run_query()
The finally guarantees the timing prints even if run_query raises.
2. Guarantee Cleanup with closing
Wrap any object that has a .close() but no context-manager support:
from contextlib import closing with closing(get_connection()) as conn: conn.execute("...") # conn.close() runs no matter what
3. Suppress Expected Errors
Replace noisy try/except/pass with intent-revealing code:
from contextlib import suppress with suppress(FileNotFoundError): os.remove("cache.tmp")
4. Stack Managers Dynamically with ExitStack
When the number of resources is not known ahead of time:
from contextlib import ExitStack with ExitStack() as stack: files = [stack.enter_context(open(p)) for p in paths] process(files) # every file closed, in reverse order
5. Use Managers for Temporary State
Set-and-restore is a perfect fit โ think switching a setting, then guaranteeing it flips back:
@contextmanager def temporary(obj, attr, value): old = getattr(obj, attr) setattr(obj, attr, value) try: yield finally: setattr(obj, attr, old)
Summary
Any "do X, then always undo X" pattern is a context manager waiting to happen. Reaching for @contextmanager, closing, suppress, and ExitStack removes a whole category of leaked-resource and forgotten-cleanup bugs โ and makes the intent obvious to the next reader.