Skip to content

Context Managers

Context managers are Python's clearest way to express resource ownership and scope. Files, DB sessions, locks, timeouts, lifespan wiring, and test cleanup can all be modeled with the same enter/exit shape.

Quick takeaway: `with` is structured `try/finally`. It is the most Pythonic way to make resource scope, transaction scope, and application lifespan explicit in code.

See the Execution Shape

A context manager acquires or prepares something on entry and guarantees cleanup on exit, whether the body succeeds or fails.

A Small Class-Based Context Manager

py
class SessionScope:
    def __enter__(self) -> str:
        print("open resource")
        return "session"

    def __exit__(self, exc_type, exc, tb) -> bool:
        print("close resource")
        return False


with SessionScope() as session:
    print("using", session)

contextlib Is Often the Cleaner Tool

py
from collections.abc import Iterator
from contextlib import contextmanager


@contextmanager
def transaction_scope() -> Iterator[str]:
    print("begin")
    try:
        yield "tx"
        print("commit")
    except Exception:
        print("rollback")
        raise
    finally:
        print("close")


with transaction_scope() as tx:
    print("inside", tx)

When the lifecycle is simple, `@contextmanager` is often more readable than a dedicated class. If you need reusable stateful objects, a class-based context manager may fit better.

Async Context Managers Matter Too

py
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager


@asynccontextmanager
async def lifespan_scope() -> AsyncIterator[str]:
    print("startup")
    try:
        yield "app-state"
    finally:
        print("shutdown")

Practical Connections

  • database session scope
  • FastAPI lifespan wiring
  • test fixtures and cleanup behavior

Checklist

Make scope explicit

If a resource has a clear open/close lifecycle, make that scope visible with `with` or `async with`.

Treat exception paths seriously

Commit vs rollback, startup vs shutdown, and acquire vs release should all be designed together.

Pick class vs helper intentionally

Classes fit reusable stateful objects; `contextlib` helpers fit simple lifecycle wrappers.

Use async context for async resources

Async clients, async DB sessions, and lifespan state should usually be managed with `async with`.

Official Sources

Built with VitePress for a Python 3.14 handbook.