Filter concepts by levelShowing all levels.

Python · Section 8

Context Managers

Guaranteed setup and cleanup around a block — for files, transactions, locks, and anything else that must be released

8 of 53
  • with obj as name:__enter__ before the block, __exit__ after — always
  • @contextmanagera generator split at yield: setup, then teardown
  • Nested managersenter left to right, exit in reverse
  • async withawaits __aenter__/__aexit__ for async setup and teardown

1.The with statement

with obj: runs obj.__enter__() before the block and obj.__exit__() after — even if the block raises. It replaces a manual try/finally for setup and teardown.

Key features

  • with obj as name: calls obj.__enter__() first; its return value becomes name
  • obj.__exit__() runs when the block ends — on normal completion AND on exception
  • Removes the need to write try/finally by hand for setup/teardown pairs
  • name is optional — with obj: is valid if the return value of __enter__ is not needed
  • Any object implementing __enter__ and __exit__ works — the protocol, not a base class

Manual try/finally vs with, for the same guarantee

Manual try/finally vs with, for the same guarantee
StyleCode shape
Manual try/finallyx = acquire(); try: use(x) finally: release(x)
with statementwith acquire() as x: use(x)
with, no bound namewith lock: do_work() # __enter__ return value unused
Multiple managerswith a() as x, b() as y: use(x, y)
with guarantees the closing half runs

__enter__()

setup — return value becomes as name

block body

may complete, or raise

__exit__()

teardown — always runs

  1. __enter__() — setup — return value becomes as name
  2. block body — may complete, or raise
  3. __exit__() — teardown — always runs

Command

python
with expression as name:
    # name is expression.__enter__()'s return value
    ...
# expression.__exit__() has already run by here, always

with obj as name: runs __enter__ before the block and __exit__ after — even on an exception. It is try/finally you do not have to write.

2.The contextlib module

contextlib is the standard library module for building context managers without a full __enter__/__exit__ class — @contextmanager, suppress(), and closing() are all here.

Key features

  • contextlib.contextmanager turns a generator function into a context manager
  • contextlib.suppress(*exceptions) ignores specific exception types inside a with block
  • contextlib.closing(obj) calls obj.close() on exit, for objects with close() only
  • contextlib.ExitStack manages a dynamic number of context managers at once
  • contextlib.nullcontext() is a do-nothing context manager, useful as a placeholder

contextlib helpers worth knowing

contextlib helpers worth knowing
HelperWhat it does
@contextmanagerTurns a generator function into a context manager (try/yield/finally)
suppress(*exc_types)Ignores the listed exception types if raised inside the block
closing(obj)Calls obj.close() on exit — for objects with close() but no __exit__
ExitStack()Holds a variable number of context managers, all closed in reverse order
nullcontext(value)Does nothing on enter/exit — a placeholder when a manager is optional

Command

python
from contextlib import contextmanager, suppress, closing, ExitStack

with suppress(KeyError):
    ...
with closing(some_obj) as obj:
    ...

contextlib is the standard library toolbox for context managers — @contextmanager, suppress(), closing(), and ExitStack cover most needs without a full class.

3.@contextlib.contextmanager

@contextmanager turns a generator function into a context manager. Code before yield runs on entry, and code after yield — wrapped in try/finally — runs on exit.

Key features

  • yield splits the function: before yield = setup, after yield = teardown
  • The value passed to yield becomes the with block's as name
  • Put teardown in a finally so it runs even if the with block raises
  • The function must yield exactly once — more or fewer is a RuntimeError
  • Needs no class, no __enter__, no __exit__ — just @contextmanager on a generator

The try/yield/finally shape

The try/yield/finally shape
PartRole
Code before yieldRuns on entry — equivalent to __enter__
yield valuePauses here; value becomes the as name
try: ... finally:Wraps the yield so teardown runs even on exception
Code after yield (in finally)Runs on exit — equivalent to __exit__
yield splits setup from teardown

before yield

setup — runs on entry

yield value

pauses; value becomes as name

after yield (finally)

teardown — always runs

  1. before yield — setup — runs on entry
  2. yield value — pauses; value becomes as name
  3. after yield (finally) — teardown — always runs

Command

python
from contextlib import contextmanager

@contextmanager
def my_manager(arg):
    # setup
    try:
        yield value          # becomes the "as" name
    finally:
        # teardown — always runs, even on exception

@contextmanager splits a generator at yield: before is setup, after (in finally) is teardown — no class, no __enter__/__exit__ needed.

4.Nested context managers

with a() as x, b() as y: enters a then b, and exits b then a — last entered, first exited. It is equivalent to nesting two with blocks.

Key features

  • with a() as x, b() as y: enters a() then b(), left to right
  • Exit order is reversed: b exits first, then a — last entered, first exited
  • Equivalent to writing with a() as x: / with b() as y: nested inside it
  • Reversed exit order matters when b depends on a still being open during teardown
  • A comma-separated with also works with parentheses: with (a() as x, b() as y):

Command

python
with a() as x, b() as y:
    ...
# exits: b first, then a

with a() as x, b() as y: enters a then b, and exits b then a — last entered, first exited, same as nesting them.

5.Async context managers and async with

async with obj: awaits obj.__aenter__() before the block and obj.__aexit__() after. Use it when setup or teardown itself needs to await something, like a network call.

Key features

  • __aenter__ and __aexit__ are both coroutines (async def), unlike __enter__/__exit__
  • async with obj as name: awaits __aenter__() first, binding its result to name
  • __aexit__() is awaited when the block ends — on normal completion AND on exception
  • async with only works inside an async def function, run via something like asyncio.run()
  • contextlib.asynccontextmanager mirrors @contextmanager for async generator functions

Command

python
class Name:
    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_value, traceback):
        return False

async def main():
    async with Name() as obj:
        ...

async with awaits __aenter__() before the block and __aexit__() after — use it when setup/teardown itself needs to await something.

6.Why context managers matter

Context managers guarantee cleanup for anything that must be released — files, database transactions, locks, network connections, temporary state — no matter how the block ends.

Key features

  • Files: with open(...) guarantees the file is closed, even if reading it raises
  • Database transactions: guarantees commit on success and rollback on failure
  • Locks: guarantees a lock is released, preventing a permanent deadlock
  • Resources (sockets, connections): guarantees a close() call even on error
  • Temporary state: guarantees a setting is restored to its original value afterward

The same guarantee, applied to six kinds of cleanup

The same guarantee, applied to six kinds of cleanup
Use caseWhat must always happen
FilesThe file handle is closed, even if reading/writing raises
Database transactionsCOMMIT on success, ROLLBACK on failure — never left half-applied
LocksThe lock is released, so other code is not permanently blocked
Resources (sockets, connections)close()/disconnect() runs, even if the code using it errors
Temporary state (env vars, config)The original value is restored after the block, pass or fail
Cleanup (general)Any "undo" step runs unconditionally, without a repeated try/finally

Command

python
with acquire_resource() as thing:
    use(thing)
# thing is released here, whether use(thing) succeeded or raised

Context managers guarantee cleanup — closing files, committing or rolling back transactions, releasing locks, restoring state — no matter how the block ends.

Remember

  • with obj as name: runs __enter__ before the block and __exit__ after — even on an exception.
  • @contextmanager splits a generator at yield: before is setup, after (in finally) is teardown.
  • with a() as x, b() as y: exits in reverse of entry order — last entered, first exited.
  • async with awaits __aenter__/__aexit__ — use it when setup or teardown itself needs to await something.

Pro tip

  • Always wrap teardown in try/finally inside a @contextmanager generator — code after a bare yield is skipped if the block raises.
  • contextlib.suppress() and closing() cover the two most common one-off cases without writing a full class or generator.
  • A context manager is the fix for a lock, connection, or transaction left open on one missed error-handling path.
  • Plain with on a class with only __aenter__/__aexit__ fails with AttributeError — use async with instead.

Next up

  • Type Hints and Static Typing

Sources

  • The with statement — docs.python.org/3/reference/compound_stmts.html#the-with-statement
  • contextlib — docs.python.org/3/library/contextlib.html
  • Every example executed on Python 3.14.3 · checked 2026-08-21

Spotted a mistake on this page? Tell us.