Generators & Context ManagersLesson 2 of 2
Context Managers
You have already used 'with open(...) as f'. That with statement works with any context manager: an object that sets something up when the block starts and is guaranteed to clean up when the block ends, whether the block finishes normally, returns early, or raises an exception. It is try/finally packaged into a reusable object.
A class becomes a context manager by defining two dunder methods. __enter__(self) runs at the start, and whatever it returns is bound to the name after as. __exit__(self, exc_type, exc, tb) runs at the end. If the block raised, the three arguments describe the exception; otherwise they are all None. If __exit__ returns True, the exception is swallowed. Return None or False (the normal case) to let it propagate.
Writing a class is often overkill. The contextlib.contextmanager decorator turns a generator into a context manager: code before the yield is the setup, the yielded value is what as receives, and code after the yield is the cleanup. Put the yield inside try/finally so cleanup also runs when the block raises.
contextlib has other gems. suppress(KeyError) silently ignores a specific exception type (a clean replacement for try/except/pass). ExitStack lets you enter a dynamic number of context managers, such as opening a list of files, and closes them all in reverse order.
In JavaScript the closest equivalent has been try/finally. JS is now adding a similar feature: using declarations with Symbol.dispose (the Explicit Resource Management proposal, already shipping in V8-based runtimes). The idea is the same: cleanup is tied to a scope, so you cannot forget it.
Quick reference
| JavaScript | Python | Note |
|---|---|---|
| try { ... } finally { cleanup() } | with resource() as r: | Cleanup is automatic |
| using r = getResource() | with get_resource() as r: | JS proposal vs Python since 2.5 |
| [Symbol.dispose]() | __enter__ / __exit__ | The protocol methods |
| try { } catch { /* ignore */ } | with suppress(KeyError): | Ignore one error type |
| No equivalent | @contextmanager | Generator-based context manager |
Side by side
// JavaScript: manual cleanup
const conn = await db.connect();
try {
await conn.query("...");
} finally {
await conn.close();
}
// Explicit Resource Management (newer runtimes)
{
using file = openFile("data.txt");
// file[Symbol.dispose]() runs at the end of the block
}# Class-based context manager
class Timer:
def __enter__(self):
import time
self.start = time.perf_counter()
return self # bound by 'as'
def __exit__(self, exc_type, exc, tb):
import time
self.elapsed = time.perf_counter() - self.start
return False # don't swallow errors
with Timer() as t:
sum(range(1_000_000))
print(f"took {t.elapsed:.3f}s")
# Generator-based (usually simpler)
from contextlib import contextmanager, suppress
@contextmanager
def opened(path):
f = open(path, encoding="utf-8")
try:
yield f # the 'as' value
finally:
f.close() # always runs
# Ignore one specific error
settings = {}
with suppress(KeyError):
del settings["missing"]Exercise
Real Python, running in your browserWrite these context managers:
<p> hello </p>
Ctrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment