Python contextlib — Core Concepts

What contextlib provides

The contextlib module offers utilities for working with Python’s context manager protocol (the with statement). It simplifies creating context managers and provides several ready-made ones for common patterns.

The star feature: @contextmanager

Without contextlib, creating a context manager requires a class with __enter__ and __exit__ methods. The @contextmanager decorator lets you write one using a simple generator function instead:

from contextlib import contextmanager
import time

@contextmanager
def timer(label):
    start = time.perf_counter()
    yield  # everything before yield is "setup", after is "cleanup"
    elapsed = time.perf_counter() - start
    print(f"{label}: {elapsed:.3f}s")

with timer("database query"):
    results = run_query()
# Prints: database query: 0.042s

The pattern:

  1. Code before yield runs on entering the with block
  2. The yielded value (if any) becomes the as variable
  3. Code after yield runs on exiting, even if an exception occurred
@contextmanager
def managed_connection(url):
    conn = connect(url)
    try:
        yield conn       # conn is available as the "as" variable
    finally:
        conn.close()     # always runs

with managed_connection("postgres://...") as conn:
    conn.execute("SELECT 1")

Always use try/finally around the yield if cleanup must happen regardless of exceptions.

Built-in context managers

contextlib.suppress(*exceptions)

Silently catches specified exceptions — cleaner than an empty except block:

from contextlib import suppress
import os

# Instead of:
try:
    os.remove("temp.txt")
except FileNotFoundError:
    pass

# Write:
with suppress(FileNotFoundError):
    os.remove("temp.txt")

contextlib.redirect_stdout / redirect_stderr

Temporarily redirects stdout or stderr to another file-like object:

from contextlib import redirect_stdout
import io

buffer = io.StringIO()
with redirect_stdout(buffer):
    print("This goes to the buffer, not the screen")

captured = buffer.getvalue()  # "This goes to the buffer, not the screen\n"

Useful for capturing output from functions that print instead of returning values.

contextlib.closing(thing)

Wraps an object that has a .close() method but isn’t already a context manager:

from contextlib import closing
from urllib.request import urlopen

with closing(urlopen("https://example.com")) as page:
    html = page.read()
# page.close() called automatically

contextlib.nullcontext(enter_result=None)

A no-op context manager. Useful as a stand-in when a context manager is optional:

from contextlib import nullcontext

def process(path, lock=None):
    cm = lock if lock else nullcontext()
    with cm:
        # process file, with or without locking
        data = open(path).read()

ExitStack: dynamic context management

ExitStack manages a variable number of context managers and cleanup callbacks. Essential when the number of resources isn’t known at coding time:

from contextlib import ExitStack

def process_files(paths):
    with ExitStack() as stack:
        files = [stack.enter_context(open(p)) for p in paths]
        # All files are open, all will be closed when the block exits
        for f in files:
            process(f)

You can also push cleanup callbacks:

with ExitStack() as stack:
    conn = connect()
    stack.callback(conn.close)
    # conn.close() will be called when exiting the block

Common misconception

@contextmanager generators must yield exactly once. If you yield zero times (early return) or more than once, you’ll get a RuntimeError. This isn’t like a normal generator — it’s a strict setup/teardown protocol:

@contextmanager
def bad_example():
    yield 1
    yield 2  # RuntimeError: generator didn't stop

@contextmanager
def also_bad():
    if False:
        yield  # RuntimeError: generator didn't yield (if condition is False)

One thing to remember

@contextmanager turns any setup/cleanup pattern into a clean with-compatible block using a generator. For multiple resources, use ExitStack. For quick exception suppression, use suppress. These cover 90% of context manager needs.

pythonstandard-libraryresource-management

See Also

  • Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
  • Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
  • Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
  • Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.
  • Python Datetime Handling Why dealing with dates and times in Python is trickier than it sounds — and how the datetime module tames the chaos