🚀AdvancedMetaprogramming

Context Managers

__enter__ / __exit__, contextlib.contextmanager, ExitStack reliable resource management and teardown.

Overview

A **context manager** is any object that implements `__enter__` and `__exit__`. The `with` statement calls `__enter__` on entry and guarantees `__exit__` is called on exit even if an exception occurs. This makes context managers the correct pattern for any resource that needs to be acquired and released: files, locks, database connections, network sockets, temporary directories. `__exit__(self, exc_type, exc_val, exc_tb)` receives exception info if one was raised inside the `with` block. Return `True` to suppress the exception; return `False` (or `None`) to let it propagate. For simple cases, `@contextlib.contextmanager` lets you write a generator-based context manager in a fraction of the code. `contextlib.ExitStack` handles a dynamic number of context managers, which is invaluable when the number isn't known at compile time. `contextlib.suppress` suppresses specified exceptions. `contextlib.redirect_stdout` / `redirect_stderr` reroute output. `contextlib.nullcontext` is a no-op context manager useful as a default value.

Key Points

  • •`with cm as x:` calls `cm.__enter__()` → `x`; `cm.__exit__()` always called on exit
  • •`__exit__(exc_type, exc_val, exc_tb)` return `True` to suppress the exception
  • •`@contextlib.contextmanager` write context manager as a generator; `yield` splits enter/exit
  • •`contextlib.ExitStack` register context managers dynamically; `stack.enter_context(cm)`
  • •`contextlib.suppress(ExceptionType)` silently ignore specified exceptions
  • •`contextlib.redirect_stdout(f)` capture or redirect stdout inside a block
  • •`contextlib.nullcontext(val)` a no-op context manager, useful as a conditional default
  • •Context managers are composable: `with open(f1) as a, open(f2) as b:`

Examples

Class-based context manager managed database connection

class DBConnection:
    def __init__(self, dsn):
        self.dsn = dsn
        self.conn = None

    def __enter__(self):
        print(f"Connecting to {self.dsn}")
        self.conn = {"dsn": self.dsn, "open": True}   # simulated
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Closing connection")
        self.conn["open"] = False
        if exc_type:
            print(f"  Error suppressed: {exc_val}")
            return True   # suppress the exception
        return False

with DBConnection("postgres://localhost/mydb") as conn:
    print("Using:", conn)
    raise RuntimeError("query failed")   # suppressed!

print("Continued after with block")

@contextmanager generator-based, far less boilerplate

from contextlib import contextmanager
import time

@contextmanager
def timer(label):
    start = time.perf_counter()
    try:
        yield                           # body of the with block runs here
    finally:
        elapsed = time.perf_counter() - start
        print(f"{label}: {elapsed:.4f}s")

with timer("loop"):
    total = sum(range(1_000_000))

print(total)

ExitStack dynamic list of context managers

from contextlib import ExitStack
import tempfile, os

filenames = ["a.txt", "b.txt", "c.txt"]

with ExitStack() as stack:
    files = [
        stack.enter_context(open(f, "w"))
        for f in filenames
    ]
    for i, f in enumerate(files):
        f.write(f"content {i}")

# All three files are closed automatically
print("All files closed")

# Clean up
for fn in filenames:
    os.remove(fn)

Common Mistakes

  • Forgetting the `try/finally` inside a `@contextmanager` if the body raises, teardown code after `yield` only runs if it's in `finally`
  • Returning a value from `__exit__` by accident any truthy return suppresses the exception, which can hide bugs
  • Using a class context manager when `@contextmanager` is cleaner and shorter

Note

The `@contextmanager` pattern: always wrap the `yield` in a `try/finally` to guarantee cleanup even when exceptions occur inside the `with` block.

Exercises

Write Python code to solve each exercise. Run it in your browser and check your output.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What is guaranteed when using a `with` statement?