with statementOpening a file, acquiring a lock, starting a database transaction — every "set up, use, then
always tear down" pattern. Java solved it with try-with-resources; Python's version is the
with block, powered by two dunder methods: __enter__ and __exit__.
The promise: cleanup runs even if the body blows up.
If you open() a file and just read from it, the file stays open until the garbage collector
happens to get to it — and if an exception fires mid-read, you might leak the handle entirely. The careful
way is a try/finally, but writing that around every resource is noisy and easy to forget. The
with statement bottles that pattern up:
A context manager is any object with two methods. On entering the block, Python calls
__enter__() and binds its return value to the as name. On leaving the block — by
finishing, by return, or by an exception — Python guarantees a call to
__exit__(). That guarantee is the whole point. Toggle whether the body raises and watch
__exit__ fire either way:
with vs hand-written try/finallyThey do the same thing — with is just the distilled, can't-forget-it version:
verbose & forgettable:
idiomatic:
You can write your own with the @contextmanager decorator (a generator that
yields once — everything before the yield is "enter," everything after is "exit"), but for now
the win is simple: wrap every file, lock, or connection in a with and you'll never leak
one.
__exit__(exc_type, exc_value, tb) receives the exception that's in flight (all three are
None on a clean exit) and its return value is a decision: return anything truthy and
Python treats the exception as handled — it stops right there, as if you'd written a matching
except; return False (or nothing — a bare function returns None,
which is falsy) and the exception propagates, exactly as if __exit__ weren't there.
That single boolean is the whole reason with can replace both "close no matter what" and
"suppress this specific error" — it's not two features, it's one return value. Spelled out, the statement
desugars to roughly this (the real version is in PEP 343):
The @contextmanager shortcut from above is that whole class generated from one
yield — but the mechanism is worth seeing once, because it's not "the function resumes": if the
body raises, Python throws that exception into the generator at the yield line, so a
try/finally wrapped around the yield is what actually guarantees cleanup, exactly as
a real __exit__ would.
Two idioms fall straight out of this contract. ExitStack lets you enter a variable number of context
managers and closes them in reverse order — same guarantee as nesting with blocks by hand,
without knowing the count up front. And atomic write — write to a temp file, then
os.replace() it over the real path only once the write finished clean — turns "guaranteed
cleanup" into "guaranteed no half-written file," because an exception before the rename never touches the
original at all.
with mgr as x: calls mgr.__enter__() (its
result → x), runs the body, then always calls mgr.__exit__() — on success
or exception. It replaces error-prone try/finally cleanup. Use it for files, locks,
connections, transactions. Builds on dunder methods and
exceptions.