How do you write a context manager using `contextlib.contextmanager`?

6 minintermediatecontext-managerscontextlib

Quick Answer

`@contextlib.contextmanager` turns a **generator function** into a context manager: code before the single `yield` runs as `__enter__` (the yielded value becomes the `as` target), and code after `yield` runs as `__exit__` — wrapping the `yield` in `try`/`finally` handles cleanup on exceptions too. It's a much more concise alternative to writing a full class with `__enter__`/`__exit__` for simple setup/teardown logic.

Detailed Answer

The generator-based shortcut

from contextlib import contextmanager
import time

@contextmanager
def timer():
    start = time.perf_counter()
    try:
        yield start           # this becomes the `as` target
    finally:
        elapsed = time.perf_counter() - start
        print(f"Elapsed: {elapsed:.3f}s")

with timer() as start_time:
    do_expensive_work()
# prints elapsed time whether do_expensive_work() succeeded or raised

Everything before yield is the "enter" logic; the yielded value is bound to the as variable; everything after yield (in the finally) is the "exit" logic — the try/finally is what guarantees the cleanup code runs even if the with block's body raises.

How exceptions flow through the generator

@contextmanager
def suppress_value_errors():
    try:
        yield
    except ValueError:
        print("suppressed a ValueError")
    # not re-raising -- suppresses it, same as __exit__ returning True

with suppress_value_errors():
    raise ValueError("oops")
print("still runs")   # exception was suppressed

If the with block raises, that exception is raised at the yield statement itself inside the generator — so a try/except wrapped around the yield can catch and optionally suppress it (by not re-raising), exactly mirroring what returning True from a class-based __exit__ would do.

Class-based vs @contextmanager: when to choose which

# Class-based -- needed when you must hold state across calls,
# or reuse the context manager as a decorator/multiple times cleanly
class ManagedResource:
    def __enter__(self): ...
    def __exit__(self, *exc_info): ...

# @contextmanager -- concise, ideal for simple setup/teardown pairs
@contextmanager
def managed_resource():
    resource = acquire()
    try:
        yield resource
    finally:
        release(resource)

@contextmanager is usually the more concise, more readable choice for straightforward "acquire, yield, release" logic; a full class is better when the context manager needs multiple methods, needs to be instantiated once and reused as a context manager many times without re-running setup, or needs to double as something else (e.g., also implementing __call__ to work as a decorator via contextlib.ContextDecorator).

Interview-ready summary: @contextmanager converts a single-yield generator into a context manager — code before yield is __enter__, code after (in a finally) is __exit__, and wrapping yield in try/except lets you intercept or suppress exceptions from the with block, exactly mirroring the class-based protocol with far less boilerplate.