Files, locks, transactions and connections must be released even when errors happen.
The with statement pairs setup with guaranteed cleanup. with expr as x: calls expr.__enter__() (binding its result to x), runs the block, and then calls __exit__ no matter how the block ends — normally, by return, or by an exception. It replaces error-prone try/finally boilerplate.
You've met it with files. The same pattern covers threading.Lock objects, database transactions, temporary directories (tempfile.TemporaryDirectory()), and patched state in tests. Several managers can share one statement: with open(a) as src, open(b, "w") as dst: (with parentheses to span lines since 3.10).
The easiest way to write one is contextlib.contextmanager on a generator: code before yield is setup, the yielded value is bound by as, and code after yield is cleanup. Wrap the yield in try/finally, otherwise cleanup is skipped when the block raises.
The class form implements __enter__(self) and __exit__(self, exc_type, exc, tb). If __exit__ returns a truthy value the exception is suppressed — rarely what you want, so return None.
contextlib has more helpers worth knowing:
suppress(FileNotFoundError)ignores a specific exceptionExitStackmanages a dynamic number of contextschdir(path)(3.11) temporarily changes directoryclosing(obj)callsobj.close()on exit
Async code has a parallel async with statement and asynccontextmanager.
Example
import time
from contextlib import contextmanager, suppress
import os
@contextmanager
def timer(label):
start = time.perf_counter()
try:
yield start # value bound by `as`
finally: # runs even if the block raises
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.3f}s")
with timer("sum"):
total = sum(range(1_000_000))
try:
with timer("failing"):
1 / 0
except ZeroDivisionError:
print("cleanup still ran")
with suppress(FileNotFoundError):
os.remove("does-not-exist.tmp")
print("no error raised")Remember
withguarantees__exit__runs, even on exceptions@contextmanager: setup,yield, cleanup infinallycontextlib.suppressandExitStackcover common needs
Watch out for
- Writing a
@contextmanagerwithout try/finally aroundyield, so cleanup is skipped on errors.