Skip to lesson
Python for JS Developers

Generators & Context ManagersLesson 2 of 2

Context Managers

4 min

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

JavaScriptPythonNote
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@contextmanagerGenerator-based context manager

Side by side

JavaScript
// 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
}
Python
# 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 browser

Write these context managers:

1. Class Tag(name): prints "<name>" on enter and "</name>" on exit, even if the block raises. It must NOT swallow exceptions. 2. @contextmanager temp_value(d, key, value): sets d[key] = value for the duration of the block, then restores the old value (or removes the key if it did not exist before), even if the block raises. 3. Use contextlib.suppress to run del cfg["missing"] on cfg = {} without crashing. 4. with Tag("p"): print("hello")
Expected output
<p>
hello
</p>

Ctrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment