Python Context Managers
Tutorial 42 of 65 · pythondeck.com Python course
Context managers acquire and release resources via the with statement. Implement __enter__ and __exit__, or use @contextlib.contextmanager on a generator. Multiple managers can be combined in a single with.
Context managers guarantee setup and teardown around a block—usually via with. They prevent leaked file handles, locks, and connections. The protocol implements __enter__/__exit__ on objects or @contextmanager generators that yield once.
The with statement calls __exit__ even when exceptions occur, optionally suppressing them if __exit__ returns true. Multiple contexts in one with line (3.10+ parenthesised form) reduce nesting.
with open(...) as f: the canonical file pattern.
contextlib.contextmanager decorator for generator-based managers.
__enter__ returns the resource; __exit__ cleans up.
contextlib.closing, suppress, ExitStack for dynamic management.
Lock acquisition: with lock: from threading.
Async context managers: async with and __aenter__/__aexit__.
contextlib.ExitStack registers many cleanup callbacks—useful when the number of files or locks is known only at runtime. Suppressing expected exceptions: with contextlib.suppress(FileNotFoundError):.
Implementing __exit__ should not swallow exceptions unless that is deliberate; log and return false to propagate. Database transactions often commit on clean exit and roll back on exception inside the manager.
Testing context managers: assert resources are released by checking side effects or mocks after the with block exits with an exception.
The standard library ships many managers—contextlib.chdir, unittest.mock.patch, and DB drivers' transaction contexts—so you rarely need bespoke teardown code for common cases.
Manual open/close instead of with when exceptions are possible.
Generator context managers without a try/finally around the yield (use @contextmanager).
Returning True from __exit__ and hiding real bugs.
Nested ten levels of with instead of ExitStack.
Using synchronous with on async resources without async with.
Always use with for files, locks, and network clients that support it.
Implement managers with @contextmanager for simple setup/teardown.
Use ExitStack when managing a dynamic list of resources.
Let exceptions propagate unless suppression is an explicit requirement.
Re-read the examples below with these ideas in mind; change variable names and inputs to match your own project.
The program below demonstrates built-in. Read the comments on each line, run the code, then change names or values to see how the output shifts.
# Example: Built-in
# Run in the REPL or save as a .py file and execute with python.
with open("hello.txt", "w") as f:
f.write("hi")
# file is closed even on errors
This sample walks through class form in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.
# Example: Class form
# Run in the REPL or save as a .py file and execute with python.
class Timer:
def __enter__(self):
import time
self.t0 = time.perf_counter()
return self
def __exit__(self, *exc):
import time
print(f"elapsed {time.perf_counter()-self.t0:.4f}s")
with Timer():
sum(i*i for i in range(1_000_000))
Here is a hands-on illustration of @contextmanager. Follow the inline comments first; only then execute the snippet and compare the result with what you expected.
# Example: @contextmanager
# Run in the REPL or save as a .py file and execute with python.
from contextlib import contextmanager
@contextmanager
def tag(name):
print(f"<{name}>")
yield
print(f"</{name}>")
with tag("h1"):
print("Hello")
The program below demonstrates custom context. Read the comments on each line, run the code, then change names or values to see how the output shifts.
# contextmanager protocol: __enter__/__exit__ or @contextmanager
from contextlib import contextmanager # helper decorator
@contextmanager # build context manager from generator
def tag(name): # printable block marker
print(f"[{name}] start") # enter
try: # yield control to with-block
yield # pause here
finally: # always runs
print(f"[{name}] end") # exit
with tag("job"): # use manager
print("working") # body
This sample walks through suppress errors in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.
# contextlib.suppress ignores specified exceptions
from contextlib import suppress # cleaner than try/except pass
values = ["1", "x", "3"] # mixed ints/strings
nums = [] # collector
for raw in values: # loop inputs
with suppress(ValueError): # skip bad int()
nums.append(int(raw)) # append on success
print(nums) # [1, 3]
from pathlib import Path # temporary directory pattern
import tempfile # stdlib temp dirs
with tempfile.TemporaryDirectory() as tmp: # auto cleanup
p = Path(tmp) / "a.txt" # path inside temp dir
p.write_text("hi", encoding="utf-8") # write
print(p.read_text(encoding="utf-8")) # read