PEP 343 in 90 seconds — with statement and the death of try/finally
Resource lifecycles, finally automatic.
Pre-3.0, releasing a file / lock / DB connection meant `try/finally` boilerplate. One forgotten `finally` and your handle leaked until the GC got bored: ```python f = open("data.csv") try: process(f) finally: f.close() # easy to forget on the 100th refactor ``` PEP 343 (Python 2.5, 2005 — backported even further than you'd think) added the `with` statement and the **context manager protocol** (`__enter__` / `__exit__`). The interpreter guarantees `__exit__` runs even if your code raises.
Any class with `__enter__` + `__exit__` is a context manager. `contextlib.contextmanager` lets you write them as generators: ```python from contextlib import contextmanager @contextmanager def timer(): import time t0 = time.perf_counter() yield # control returns here while body runs print(f"{time.perf_counter() - t0:.3f}s") ``` Every library that handles a resource — files, locks, transactions, HTTP sessions, GPU contexts — leans on this. It's so foundational that 3.10 added `async with` for the asyncio side.
# Pre-PEP-343 — manual lifecycle
import threading
lock = threading.Lock()
lock.acquire()
try:
do_critical_work()
finally:
lock.release() # forget this and your thread holds it forever
# And the same shape for files, DB connections, sockets, GPU contexts.import threading
lock = threading.Lock()
# Context manager handles acquire + release automatically.
with lock:
do_critical_work()
# Released here, even if do_critical_work raised.
# Same shape, every resource type:
with open("data.csv") as f:
process(f)
# Or build your own:
class TimerCtx:
def __enter__(self):
self.events = []
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.events.append("exit")
return False # don't suppress exceptions🎯 Predict the output
What does this print? `__exit__` runs BOTH when the body returns normally AND when it raises — guaranteed.
class TimerCtx:
def __enter__(self):
self.events = []
self.events.append("enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.events.append("exit")
return False
with TimerCtx() as t:
t.events.append("inside")
print(t.events)
print(t.events)