Context Managers
The problem context managers solve
Section titled “The problem context managers solve”Many operations must be paired with a cleanup step: open a file then close it, acquire a lock then release it, start a timer then stop it. If an exception occurs between the two steps, the cleanup never runs — leading to resource leaks.
The with statement solves this by guaranteeing that the cleanup runs no matter what.
with open("data.txt") as f: content = f.read()# f is always closed here, even if read() raisedHow with works: the protocol
Section titled “How with works: the protocol”Any object with __enter__ and __exit__ methods is a context manager.
with expr as var: bodyThis is exactly equivalent to:
cm = exprvar = cm.__enter__()try: bodyexcept: if not cm.__exit__(*sys.exc_info()): raiseelse: cm.__exit__(None, None, None)__enter__ sets up the resource and returns a value bound to var.
__exit__ always runs on exit. If it returns a truthy value it suppresses the exception; returning False (or None) lets the exception propagate.
Writing a class-based context manager
Section titled “Writing a class-based context manager”class Timer: def __enter__(self) -> "Timer": print("Timer started") return self
def __exit__(self, exc_type, exc_val, exc_tb) -> bool: print("Timer stopped") return False # do not suppress exceptionsThe three arguments to __exit__ are the exception type, value, and traceback — all None when no exception occurred.
contextlib.contextmanager — the generator shortcut
Section titled “contextlib.contextmanager — the generator shortcut”Writing a full class just to wrap setup/teardown is verbose.
contextlib.contextmanager lets you use a generator function instead:
import contextlib
@contextlib.contextmanagerdef managed_resource(name: str): print(f"Acquiring {name}") try: yield name # value bound to the as-variable finally: print(f"Releasing {name}")Everything before yield is __enter__ logic. Everything after yield (in the finally) is __exit__ logic.
The try/finally inside the generator ensures the teardown runs even when an exception is raised inside the with block.
Nesting context managers
Section titled “Nesting context managers”Multiple context managers can be combined on one with line — they nest left to right:
with open("in.txt") as src, open("out.txt", "w") as dst: dst.write(src.read())Full runnable demo
Section titled “Full runnable demo”import contextlib
# --- class-based context manager ---class Timer: def __enter__(self): print("Timer started") return self def __exit__(self, exc_type, exc_val, exc_tb): print("Timer stopped") return False
print("=== class-based ===")with Timer(): print(" doing work...")
# --- contextlib.contextmanager ---@contextlib.contextmanagerdef managed_resource(name: str): print(f"Acquiring {name}") try: yield name finally: print(f"Releasing {name}")
print("\n=== contextmanager generator ===")with managed_resource("connection") as res: print(f" using {res}")
# --- cleanup on error ---print("\n=== cleanup runs even on error ===")try: with managed_resource("db") as res: print(f" using {res}") raise RuntimeError("something failed")except RuntimeError as e: print(f" caught: {e}")Loading Python runtime (first run only)…