Acquire and reliably release a resource (file, lock, DB, transaction), even on exception.
Bouncers who let you in, watch you dance, and clean up after you leave even if you pass out.
with cm as obj:
body __enter__ runs
... __exit__ ALWAYS runs
(even on exception)
The with statement calls __enter__, binds the result, runs the body, ALWAYS calls __exit__ - even on exception. Build your own with @contextmanager (generator form - yield is the seam) or implement __enter__/__exit__ directly. contextlib.ExitStack composes multiple (great for dynamic resource sets).
with open('data.txt') as f:
text = f.read()
# f closed automatically, even on exception
canonical file use; closed automatically.
from contextlib import contextmanager
import time
@contextmanager
def timer(label):
s = time.perf_counter()
try: yield
finally: print(label, time.perf_counter()-s, 's')
with timer('load'):
load_huge()
@contextmanager turns any single-yield generator into a CM.
from contextlib import ExitStack
paths = ['a.txt', 'b.txt']
with ExitStack() as stack:
files = [stack.enter_context(open(p)) for p in paths]
process(files)
# all files closed in reverse
ExitStack stacks CMs and unwinds in reverse; great for dynamic resource sets.
__exit__ returning True swallows exceptions silently - the caller never finds out. CMs are clean-up tools, NOT error-handlers.
with for files/locks/DB transactions; @contextmanager for one-off resources; ExitStack for variable-count sets.
FATAL: remaining connection slots reserved; API 500s.conn = pool.get(); conn.execute(...) without try/finally.with pool.connection() as conn:.pool.get() without with; integration test triggers 1000 errors.