Skip to Content

Context Managers

When to use

Acquire and reliably release a resource (file, lock, DB, transaction), even on exception.

Analogy

Bouncers who let you in, watch you dance, and clean up after you leave even if you pass out.

Data-flow diagram

with cm as obj:
    body            __enter__ runs
    ...             __exit__ ALWAYS runs
                    (even on exception)

Deep explanation

The with statement calls __enter__, binds the result, runs the body, ALWAYS calls __exit__ - even on exception. Build your own with @contextmanager (generator form - yield is the seam) or implement __enter__/__exit__ directly. contextlib.ExitStack composes multiple (great for dynamic resource sets).

Examples

Example 1

with open('data.txt') as f:
    text = f.read()
# f closed automatically, even on exception

canonical file use; closed automatically.

Example 2

from contextlib import contextmanager
import time
@contextmanager
def timer(label):
    s = time.perf_counter()
    try: yield
    finally: print(label, time.perf_counter()-s, 's')
with timer('load'):
    load_huge()

@contextmanager turns any single-yield generator into a CM.

Example 3

from contextlib import ExitStack
paths = ['a.txt', 'b.txt']
with ExitStack() as stack:
    files = [stack.enter_context(open(p)) for p in paths]
    process(files)
# all files closed in reverse

ExitStack stacks CMs and unwinds in reverse; great for dynamic resource sets.

Common mistake

__exit__ returning True swallows exceptions silently - the caller never finds out. CMs are clean-up tools, NOT error-handlers.

Key takeaway

with for files/locks/DB transactions; @contextmanager for one-off resources; ExitStack for variable-count sets.

Production Failure Playbook

Failure scenario 1: connection-leak-on-exception

Failure scenario 2: silent-exit-returns-true