C.W.K.
Stream
Lesson 04 of 06 · published

Context Managers — with, __enter__, __exit__

~20 min · context-manager, with, enter, exit, cleanup

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The with statement — guaranteed cleanup

with open(path) as f: opens a file and guarantees it's closed when the block exits — exception or not. The mechanism: the object after with is a context manager, and Python calls its __enter__ on entry and __exit__ on exit. Cleanup is the killer use case, but it's not the only one — anything where you need "set up, then tear down regardless" can be a context manager.

The protocol — __enter__ returns the resource, __exit__ cleans up

A class becomes a context manager by defining __enter__(self) (returns the value bound by as) and __exit__(self, exc_type, exc_value, tb) (cleanup; receives exception info if there was one). If __exit__ returns truthy, the exception is suppressed; if falsy or None, it propagates.

Multiple contexts in one with

with open(a) as fa, open(b) as fb: opens both, closes both. The grammar handles arbitrary nesting flat. Order matters — they're entered left-to-right, exited right-to-left. If the second open raises, the first file is still cleanly closed.

What context managers replace

Anything that used to be try: ... finally: cleanup(). File handles, locks, database transactions, temporary chdir, redirecting stdout, opening connections. The finally pattern is correct; the context manager pattern is just a more idiomatic, more reusable, less error-prone way to spell it.

Principle: If you find yourself writing try: ... finally: cleanup() — and the cleanup logic is reusable — write a context manager. The next caller (which might be future-you) gets the cleanup automatically without having to remember.

Code

Class-based context manager·python
class Timer:
    def __enter__(self):
        import time
        self.start = time.perf_counter()
        return self

    def __exit__(self, exc_type, exc_value, tb):
        import time
        elapsed = time.perf_counter() - self.start
        print(f"elapsed: {elapsed*1000:.2f}ms")
        # return None / False — exceptions propagate
        return False

with Timer() as t:
    sum(range(1_000_000))
# elapsed: 12.34ms
Multiple contexts in one with·python
import contextlib, io

# Open two files, both cleanly closed
fa = io.StringIO("hello")
fb = io.StringIO("world")

with fa, fb:                 # both context-managed, both close on exit
    print(fa.read(), fb.read())

# Right-to-left exit order: fb closes first, then fa.
# If fb's __enter__ raises, fa's __exit__ still runs.
Suppressing exceptions — __exit__ returns truthy·python
class Suppress:
    def __init__(self, *types):
        self.types = types

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, tb):
        return exc_type is not None and issubclass(exc_type, self.types)
        # truthy => suppress the exception

with Suppress(ValueError):
    int("not a number")        # ValueError suppressed
    print("this line still runs after")

# This is roughly what contextlib.suppress does — and you should use that instead
# of writing it yourself
with as cleanup pattern — replacing try/finally·python
# Old style — manual cleanup
f = open("data.txt")
try:
    contents = f.read()
finally:
    f.close()

# New style — automatic
with open("data.txt") as f:
    contents = f.read()
# f is closed here — exception or not

External links

Exercise

Build a class-based context manager ChDir(path) that temporarily changes the working directory inside the with block and restores it on exit, even if an exception is raised. (os.getcwd() and os.chdir() are your friends.) Test by entering a different directory inside the block and verifying that on exit, os.getcwd() is back to where it started — even when the inner block raises.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.