Learn by reading through in order

contextlib — Resource Management and Custom with

Write your own with without a class via @contextmanager and yield, run cleanup safely with try / finally, and swallow specific exceptions with suppress.

contextlib is the module to reach for when you want to write your own with statement. We'll walk through its two simplest tools in order: @contextmanager for the simplest way to define one, and suppress for swallowing specific exceptions.

@contextmanager — define a with statement easily

Python's with statement is the safe way to handle "things you have to clean up after using" — file open, DB connections, lock acquisition, and so on. To make a class work with with by hand, you need to define __enter__ / __exit__, which is a lot of boilerplate. @contextmanager is a shortcut: write a single generator (a function that contains yield) and you get a context manager that works with with. The code before yield is "start of the action", and the code after is "the cleanup".

@contextmanager structure
@contextmanagerdef section(name):Setup(before yield)yieldTeardown(after yield)
A generator function plus @contextmanager — the part before yield is the __enter__ equivalent (setup), and the part after yield is the __exit__ equivalent (teardown). The yield value is what gets bound by as in the with statement.
from contextlib import contextmanager

# --- Reference: building a with-compatible class ----------
# class Section:
#     def __init__(self, name):
#         self.name = name
#     def __enter__(self):
#         print(f"--- {self.name} start ---")
#         return self                 # value bound by with's `as`
#     def __exit__(self, exc_type, exc, tb):
#         print(f"--- {self.name} end ---")
# ----------------------------------------------------------

# @contextmanager version: one generator function does the same job
@contextmanager
def section(name):
    print(f"--- {name} start ---")
    yield                       # the with body runs here
    print(f"--- {name} end ---")

# Usage
with section("aggregation"):
    total = sum(range(100))
    print("Total:", total)

# Output:
# --- aggregation start ---
# Total: 4950
# --- aggregation end ---
Class version → @contextmanager version simplification
Class version__init__ / __enter__ /__exit__ — 3 methodsSimplify with@contextmanagerGenerator versionbefore yield = setupafter yield = teardown
Replacing a class with __init__ / __enter__ / __exit__ (three methods) with a single generator function decorated by @contextmanager maps the code before/after yield directly to setup/teardown — far less code.

Use try/finally to guarantee cleanup even on exceptions

The code after yield may not run if an exception is raised inside the with block. For cleanup that must always happen — closing files, releasing locks — wrap the body of the generator in try: yield finally: cleanup() for safety.

Build a transaction(name) modeling a DB transaction with @contextmanager. Print BEGIN before and COMMIT after, with the SQL inside the block visible between them.

① Import contextmanager from contextlib

② Define a transaction(name) function decorated with @contextmanager — print [name] BEGIN, then yield, then print [name] COMMIT after

③ Inside with transaction("order_create"):, print two SQL lines: INSERT INTO orders (id, total) VALUES (1, 1980) and UPDATE inventory SET stock = stock - 1

(If your code runs correctly, the explanation will appear.)

Python Editor

Run code to see output

suppress — swallow specific exceptions

contextlib.suppress(ExceptionType, ...) is a context manager for swallowing specified exceptions and continuing execution. The pattern of "ignore just this exception and move on" — equivalent to try: ... except KeyError: pass — fits in a single line. You can list multiple exception types at once, and anything outside the list propagates normally.

from contextlib import suppress
import os

# "Run with default values even if the file doesn't exist"
config = {"theme": "light", "font_size": 14}
with suppress(FileNotFoundError):
    with open("user_config.txt") as f:
        config["theme"] = f.read().strip()
# Won't crash if user_config.txt is missing — config keeps its defaults
print(config)
# → {'theme': 'light', 'font_size': 14}

# "Skip silently if already deleted"
with suppress(FileNotFoundError):
    os.remove("old.tmp")
print("Delete done (OK if it didn't exist)")

Use suppress to keep going even when pulling missing keys from a dict.

① Import suppress from contextlib

② Define a dict user = {"name": "Alice", "email": "alice@example.com"} (note: no age key)

③ Inside with suppress(KeyError):, try accessing user["age"] — confirm that the missing key doesn't crash

④ Right after the block, print Continuing: nothing crashed

⑤ Print the user's name and email on a single line as User: ◯ <◯>

Python Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1When you decorate a generator function with @contextmanager, why does it need to yield exactly once?

Q2Inside with suppress(KeyError):, what happens when KeyError is raised?