Skip to content

contextlib — Utilities for with-statement Contexts

The contextlib module provides utilities for working with the with statement and context managers. While implementing custom context managers from scratch requires classes with __enter__ and __exit__ dunder methods, contextlib delivers convenient decorators, reusable helper contexts, and dynamic resource stacks.


Core Decorators

@contextmanager

Converts a generator function containing a single yield statement into a complete context manager:

from contextlib import contextmanager
import time

@contextmanager
def benchmark(label: str):
    start = time.perf_counter()
    try:
        yield  # Code inside the `with` block executes here
    finally:
        elapsed = time.perf_counter() - start
        print(f"[{label}] Finished in {elapsed:.4f}s")

with benchmark("Matrix Multiplication"):
    matrix = [[i * j for j in range(500)] for i in range(500)]
[Matrix Multiplication] Finished in 0.0215s

@asynccontextmanager

The asynchronous equivalent for coroutines running in asyncio:

from contextlib import asynccontextmanager
import asyncio

@asynccontextmanager
def async_timer(label: str):
    start = asyncio.get_event_loop().time()
    try:
        yield
    finally:
        elapsed = asyncio.get_event_loop().time() - start
        print(f"[{label}] Async task took {elapsed:.4f}s")

Standard Context Manager Helpers

1. contextlib.suppress(*exceptions)

Replaces verbose try...except Pass patterns when safely ignoring anticipated, non-critical exceptions:

from contextlib import suppress
import os

# Cleanly remove file without crashing if it doesn't exist:
with suppress(FileNotFoundError):
    os.remove("temporary_cache.tmp")
import os

try:
    os.remove("temporary_cache.tmp")
except FileNotFoundError:
    pass

2. contextlib.chdir(path) (Python 3.11+)

Safely changes the current working directory for the duration of the with block, restoring the previous directory upon exit:

from contextlib import chdir
import os

print(f"Current dir: {os.getcwd()}")

with chdir("/tmp"):
    print(f"Inside with: {os.getcwd()}")

print(f"Restored dir: {os.getcwd()}")

3. contextlib.redirect_stdout & redirect_stderr

Temporarily redirects standard output or error to any file-like object or string buffer:

from contextlib import redirect_stdout
import io

buffer = io.StringIO()

with redirect_stdout(buffer):
    print("This message will be intercepted by the buffer.")
    help(str.upper)

captured = buffer.getvalue()
print(f"Captured {len(captured)} characters from help output.")

4. contextlib.closing(thing)

Wraps any object providing a .close() method that is not natively a context manager (e.g. urllib.request.urlopen or legacy database connections):

from contextlib import closing
from urllib.request import urlopen

with closing(urlopen("https://www.python.org")) as page:
    for line in page.readlines()[:5]:
        print(line)

5. contextlib.nullcontext(enter_result=None)

A dummy context manager that does nothing. It is essential when a context manager is optional or conditional, avoiding duplicate code branches:

from contextlib import nullcontext

def process_data(stream=None):
    # If no stream passed, write to stdout; otherwise enter the stream context
    target = nullcontext(sys.stdout) if stream is None else stream

    with target as out:
        out.write("Processing finished.\n")

Dynamic Resource Management: ExitStack

When you need to manage a variable or unknown number of context managers at runtime (for example, opening an arbitrary list of files passed by the user), nesting with statements statically is impossible.

ExitStack maintains a dynamic stack of context managers and guarantees they are all cleaned up in reverse order:

from contextlib import ExitStack
from pathlib import Path

def merge_files(input_filenames: list[str], output_filename: str):
    with ExitStack() as stack:
        # Dynamically open and manage all input files
        input_files = [
            stack.enter_context(open(fname, "r", encoding="utf-8"))
            for fname in input_filenames
        ]
        # Open output file
        out_file = stack.enter_context(open(output_filename, "w", encoding="utf-8"))

        # Write lines from each file
        for f in input_files:
            out_file.write(f.read())
            out_file.write("\n")

    # All files are guaranteed closed here, even if an exception was raised!

Registering Callbacks in ExitStack

You can register arbitrary cleanup callbacks that execute upon exit:

with ExitStack() as stack:
    stack.callback(print, "Cleanup step 1 executed")
    stack.callback(print, "Cleanup step 2 executed")
Cleanup step 2 executed Cleanup step 1 executed

For asynchronous code, use AsyncExitStack.


Summary of contextlib Utilities

Utility Purpose Introduced
@contextmanager Turn generator into context manager Python 2.5
@asynccontextmanager Turn async generator into async context manager Python 3.7
suppress(*exc) Mute expected exceptions Python 3.4
closing(obj) Calls .close() upon block exit Python 2.5
nullcontext() Dummy no-op context manager Python 3.7
redirect_stdout(s) Intercept stdout into a buffer/file Python 3.4
redirect_stderr(s) Intercept stderr into a buffer/file Python 3.5
chdir(path) Temporarily switch working directory Python 3.11
ExitStack Programmatically enter dynamic number of contexts Python 3.3
AsyncExitStack Dynamic async context manager stack Python 3.7