Context Managers
The with statement and how it guarantees setup and cleanup — no matter what happens inside the block.
"with is a promise: whatever happens inside this block, I will clean up when it exits. No matter if it succeeds or crashes."
— ShurAIThe Problem: Forgetting to Clean Up
When working with files, database connections, or network sockets, you must always clean up — close the file, release the connection. But if an error happens, you might forget:
f = open("data.txt")
# ... if something crashes here ...
f.close() # this line is never reached! File stays open.
A context manager guarantees cleanup through the with statement:
with open("data.txt") as f:
content = f.read()
# ... even if an error happens here ...
# file is ALWAYS closed when the block exits
How with Works Under the Hood
with block. Sets things up and optionally returns a value (the as f part).Building Your Own Context Manager (Class-Based)
class Timer:
"""Measures how long a block of code takes."""
import time
def __enter__(self):
import time
self.start = time.time()
return self # returned as the 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.elapsed = time.time() - self.start
print(f"Elapsed: {self.elapsed:.4f}s")
return False # False = don't suppress exceptions
with Timer() as t:
total = sum(range(1_000_000))
# Elapsed: 0.0312s
The Easy Way — @contextmanager
Use contextlib.contextmanager to build a context manager from a generator function. The code before yield is setup; after is cleanup:
from contextlib import contextmanager
@contextmanager
def managed_file(filename):
print(f"Opening {filename}")
f = open(filename, "w")
try:
yield f # ← this is the "as f" value
finally:
f.close() # always runs, even on error
print(f"Closed {filename}")
with managed_file("notes.txt") as f:
f.write("Hello from context manager!")
# Opening notes.txt
# Closed notes.txt
Real Example — Database Transaction
from contextlib import contextmanager
@contextmanager
def transaction(db):
"""Commit on success, rollback on any error."""
print("BEGIN transaction")
try:
yield db
print("COMMIT")
except Exception as e:
print(f"ROLLBACK — {e}")
raise
# Simulated DB object
class FakeDB:
def insert(self, row):
print(f" Inserting: {row}")
db = FakeDB()
with transaction(db) as conn:
conn.insert("User: Riya")
conn.insert("User: Arjun")
BEGIN transaction
Inserting: User: Riya
Inserting: User: Arjun
COMMIT
"Whenever you find yourself writing try/finally just to ensure cleanup, that's a context manager waiting to happen. Extract it, name it, and reuse it anywhere."
— ShurAI🧠 Quiz — Q1
What problem does a context manager solve?
🧠 Quiz — Q2
What does the as f part of with open("file") as f give you?
🧠 Quiz — Q3
In a @contextmanager generator, what is the role of yield?
🧠 Quiz — Q4
When does __exit__ run?