Python Docs
Context Managers
Context managers guarantee proper acquisition and release of resources. They are used with the with statement and handle setup/teardown logic safely even when exceptions occur.
Basic Usage
The most common example is working with files. The file will always be closed when the block exits.
# File automatically closes after block
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("hello")- If an exception happens inside the block, the file is still safely closed.
- The object returned by
open(...)is bound tof.
Creating a Context Manager via Class
A custom context manager class implements __enter__ and __exit__.
class Managed:
def __enter__(self):
print("acquire")
return self
def __exit__(self, exc_type, exc, tb):
print("release")
# Return True to suppress exception, False to propagate
return False
with Managed() as m:
print("work")__enter__runs at the start; its return value becomesas m.__exit__(exc_type, exc, tb)always runs, even if an exception happens.- Returning
Trueswallows the exception (use carefully).
Using contextlib.contextmanager
You can write context managers using a generator function with a yield instead of a class.
from contextlib import contextmanager
@contextmanager
def db_session(engine):
conn = engine.connect()
try:
yield conn # code inside "with" block runs here
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()with db_session(engine) as conn:
conn.execute("INSERT INTO logs(message) VALUES ('ok')")- Code before
yieldis setup (acquire). - Code after
yieldruns on exit (teardown). - Exceptions inside the
withare caught in theexceptblock.
Helpful Tools in contextlib
from contextlib import suppress, ExitStack, closing
import socket
# Suppress a specific exception
with suppress(FileNotFoundError):
open("missing.txt").read()
# Manage many contexts dynamically
files = ["a.txt", "b.txt"]
with ExitStack() as stack:
handles = [stack.enter_context(open(p, "w")) for p in files]
handles[0].write("hello")
# Ensure close() is called on arbitrary objects
with closing(socket.socket()) as s:
s.bind(("127.0.0.1", 0))suppressis useful for ignoring expected errors (e.g., missing files).ExitStackis great when you don’t know in advance how manywithcontexts you'll need.closingadapts any object with.close()into a context manager.
Real-World Examples
import threading
from contextlib import contextmanager
lock = threading.Lock()
@contextmanager
def locked():
lock.acquire()
try:
yield
finally:
lock.release()
with locked():
# critical section
print("Only one thread at a time")import time
from contextlib import contextmanager
@contextmanager
def timed(label: str):
start = time.perf_counter()
try:
yield
finally:
duration = time.perf_counter() - start
print(f"{label} took {duration:.4f}s")
with timed("heavy_task"):
sum(range(10_000_000))Best Practices
- Prefer
withfor files, locks, DB sessions, network sockets, temporary directories, etc. - Make
__exit__/ teardown code idempotent and exception-safe. - Be careful when suppressing exceptions; document clearly what is being suppressed and why.
- If your setup/cleanup logic repeats everywhere → wrap it in a context manager.