Focused practice · 9 min read

Python Context Manager Practice Guide

Cleanup is easy to forget on the success path and even easier to miss when an exception interrupts the function. A context manager puts setup and teardown around a block so the cleanup has one clear home.

Use with for paired operations

The with statement opens a resource, runs a block, and closes it when the block ends. Files are the canonical example, but the same pattern applies to locks, temporary directories, and transactions. Keep the block focused on the work that needs the resource.

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

A managed file

Implement the protocol

__enter__ prepares the resource and returns the value bound by as. __exit__ receives the exception type, value, and traceback, then performs cleanup. A false return value lets an exception propagate; a true value suppresses it.

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

with Guard():
    print("inside")

Setup and teardown

Cleanup still runs on error

The body does not need a try/finally around every resource operation. If the body raises, __exit__ is still called before the exception continues. That is the guarantee that makes with useful for correctness, not just brevity.

class Guard:
    def __enter__(self):
        return self
    def __exit__(self, *args):
        print("cleaned")

try:
    with Guard():
        raise ValueError("boom")
except ValueError:
    print("caught")

Cleanup before the error escapes

Build one with contextmanager

Decorate a generator with contextlib.contextmanager. Code before yield is setup; code after yield is cleanup. The yielded value is available through as. Keep exactly one yield and put cleanup after it.

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 generator-based manager

Do not hide exceptions by accident

Returning True from __exit__ suppresses the exception. That can be useful for a deliberate policy, but it can also hide bugs. Return None or False when the caller should see the error. Document any suppression clearly.

class Visible:
    def __enter__(self):
        return self
    def __exit__(self, *args):
        return False

with Visible():
    print("still visible")

Let errors propagate

Your practice checklist

Common questions

Is with only for files?

No. Any object implementing the context manager protocol can be used, including locks, database transactions, and test fixtures.

Can a context manager suppress an exception?

Yes, if __exit__ returns a true value. Use that sparingly and document it.

What happens if __enter__ raises?

The body is not entered, and __exit__ is not called. The exception propagates to the caller.

Practice context managers

12 questions on with, __enter__, __exit__, cleanup, and contextlib.

Open practice

Keep reading: