Guaranteed setup and teardown — the Pythonic way to manage any resource
Context Managers are how Python guarantees resource cleanup even when exceptions occur. Open files, database connections, locks, GPU memory, network sessions, temp directories — all should be managed with with. Without it, a single exception leaves resources leaked.
Writing your own context managers unlocks patterns that appear everywhere in production code: database transaction rollback, profiling blocks, suppressing specific exceptions, and async resource management. The protocol is small but the design implications are large — a good mental model for how Python handles resource lifetimes.
The with statement is a guaranteed try/finally with a cleaner name and a reusable protocol. It calls __enter__ on entry (setup, returns the resource) and __exit__ on exit (cleanup) — regardless of whether the block raised an exception.
Think of it as a contract: "I promise to clean up after myself, even if something goes wrong inside." The context manager holds that promise so every call site doesn't have to remember to.
The context manager protocol is two methods: __enter__ for setup and __exit__ for teardown. Python calls them automatically around the with block — even if an exception is raised.
__enter__ runs setup and returns the value bound to the as variable. __exit__ always runs — it receives exception info (or None) and returns False/None to let exceptions propagate, True to suppress them.
Writing a full class just for a context manager is verbose. The @contextmanager decorator turns a generator function into one — code before yield is setup, code in finally is guaranteed teardown.
Code before yield is __enter__. Code in finally after yield is __exit__. Much less boilerplate than writing a full class. The try/finally ensures cleanup even if the block raises.
The contextlib module ships ready-made helpers for common context manager patterns — suppressing specific exceptions, optional managers, and managing a dynamic number of resources.
suppress is a clean alternative to a bare try/except: pass. nullcontext lets code optionally use a context manager without duplicating the inner block. ExitStack manages a variable number of resources opened at runtime.
Async context managers work identically to sync ones but use async with — needed when setup or teardown involves awaitable IO like opening a database connection or HTTP session.
Async context managers use async with and implement __aenter__ / __aexit__ (both async). The @asynccontextmanager decorator works the same as its sync equivalent but with async def and await inside.