Lesson 28 · Intermediate · 20 min

Python Context Managers

Guarantee setup and cleanup with a with block.

context managerswith__enter____exit__

The receipt printer jams halfway through a write, and the file stays open. A with block promises cleanup even when the body fails. What exactly does the context manager guarantee?

A with block runs setup, executes a body, and guarantees cleanup even when the body raises an exception. Files are the everyday example, but the same protocol works for locks, temporary state, database transactions, and test fixtures. You will also suppress one expected error, clean up on failure, and manage a dynamic number of resources.

Let with close the file

The with statement calls the file's context manager methods for you. The file is closed when the block ends, including when an exception escapes. You do not need a separate close call on every path.

from pathlib import Path
path = Path("pypractice-context.txt")
path.write_text("hello\n", encoding="utf-8")
with path.open(encoding="utf-8") as handle:
    print(handle.read().strip())
path.unlink()

Automatic cleanup

Understand enter and exit

A context manager implements __enter__, which prepares the resource, and __exit__, which receives exception details and performs cleanup. A with statement calls them around the body. Returning a false value from __exit__ lets an exception propagate.

class Guard:
    def __enter__(self):
        print("enter")
        return self
    def __exit__(self, exc_type, exc, tb):
        print("exit")

with Guard():
    print("inside")

The context manager protocol

Build one from a generator

contextlib.contextmanager turns a generator into a context manager. Everything before yield is setup, and everything after yield is cleanup. The value yielded becomes the value bound by as.

from contextlib import contextmanager

@contextmanager
def label(name):
    print("start", name)
    yield name
    print("end", name)

with label("demo") as value:
    print("body", value)

A compact custom manager

__exit__ can suppress one error

Returning True from __exit__ suppresses the exception. Use that power only for a narrow, expected failure, such as a missing optional file. Returning True for every exception hides bugs; contextlib.suppress expresses the same intent with less code.

class IgnoreMissing:
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc, tb):
        if exc_type is FileNotFoundError:
            print("ignored missing file")
            return True
        return False

with IgnoreMissing():
    raise FileNotFoundError("demo")
print("after")

Suppress only the expected type

Cleanup runs when the body fails

A contextmanager function should put cleanup in finally around yield. The code after yield runs on normal exit and on exception, but only if control returns through the generator. Catching the exception outside the with block keeps cleanup separate from error handling.

from contextlib import contextmanager

@contextmanager
def session():
    print("open")
    try:
        yield "ready"
    finally:
        print("close")

try:
    with session() as state:
        print(state)
        raise RuntimeError("boom")
except RuntimeError as error:
    print("caught", error)

yield inside try, cleanup in finally

ExitStack manages a dynamic list

When the number of resources is not known until runtime, ExitStack can register each one. It closes them in reverse order when the block exits. This avoids writing a variable-depth nest of with statements.

from contextlib import ExitStack
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as tmp:
    root = Path(tmp)
    with ExitStack() as stack:
        handles = []
        for name in ["a.txt", "b.txt"]:
            handle = stack.enter_context((root / name).open("w", encoding="utf-8"))
            handles.append(handle)
            handle.write(name)
        print(len(handles), "open")
    print("all closed")

Register resources as you create them

Key points

  • with guarantees cleanup when the block exits.
  • A context manager implements __enter__ and __exit__.
  • __exit__ receives exception information and can suppress it.
  • contextlib.contextmanager builds a manager from a generator with one yield.
  • Suppress narrowly; put cleanup in finally; use ExitStack for dynamic resources.

Common mistakes

  • Opening a file without a with block and forgetting to close it on every path.
  • Thinking __exit__ runs only when there is no exception.
  • Forgetting that the code after yield in a contextmanager runs during cleanup.
  • Returning True from __exit__ for every exception and hiding real bugs.
Your turn

Open a small file with with, read it, and delete it after the block.

Try it yourself

Change the values, predict the output, then run the code. The Python runtime is downloaded the first time you press Run, so the first run can take a few seconds.

In-browser PythonRuns locally · no account
First run downloads Pyodide from a CDN. Use Run code after editing.

Practice this lesson

12 questions with immediate explanations.

See all 12 answersStart practice
← decoratorsdataclasses →