Context Managers & with

Guarantee setup and cleanup with the with statement — via __enter__/__exit__ or @contextmanager.

The with statement guarantees that cleanup happens — even if an exception is raised. You already use it for files: with open(path) as f: closes the file no matter what. You can give your own objects the same superpower.

The two ways to build one

1. The class protocol. Implement __enter__ (runs on entry, its return value is bound to as) and __exit__ (always runs on the way out, even on error):

class Timer:
    def __enter__(self):
        self.start = time.perf_counter()
        return self
    def __exit__(self, exc_type, exc, tb):
        self.elapsed = time.perf_counter() - self.start
        # return False to let any exception propagate

2. The decorator way. @contextlib.contextmanager turns a generator into a context manager: everything before yield is setup, everything after is teardown, and the yielded value is what as receives.

The __exit__ return value

If __exit__ returns a truthy value, it swallows the exception. Return False (or nothing) to let errors propagate — that is almost always what you want.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • A transaction context manager commits on success and rolls back on exception without try/finally boilerplate.
  • A temporary file context manager creates a file, yields its path, and deletes it when the block exits.
  • A test isolation helper uses contextlib.ExitStack to open a variable number of context managers in one with.

More examples

contextmanager for transactions

Implements a commit/rollback transaction context with @contextmanager and a try/except/yield.

Example · python
from contextlib import contextmanager

@contextmanager
def transaction(conn):
    try:
        yield conn
        conn['commit'] = True
        print('Committed')
    except Exception:
        conn['rollback'] = True
        print('Rolled back')
        raise

conn = {}
with transaction(conn):
    conn['sql'] = 'INSERT ...'
print(conn)   # {'commit': True, 'sql': 'INSERT ...'}

Suppress specific exceptions

Uses contextlib.suppress() to silently ignore a FileNotFoundError instead of try/except/pass.

Example · python
from contextlib import suppress
import os

with suppress(FileNotFoundError):
    os.remove('nonexistent.tmp')
print('Continues without error')

contextlib.ExitStack for dynamic cleanup

Uses ExitStack to open a variable number of files and guarantee they all close when the block exits.

Example · python
from contextlib import ExitStack

filenames = ['a.txt', 'b.txt']
with ExitStack() as stack:
    files = [stack.enter_context(open(f, 'w'))
             for f in filenames]
    files[0].write('hello')
    files[1].write('world')
print('All files closed')

Discussion

  • Be the first to comment on this lesson.