What is a context manager and how does the with statement work?
A context manager is any object that implements __enter__ and __exit__. The with statement calls __enter__ on entry and __exit__ on exit — guaranteed, even if an exception is raised. This makes with the idiomatic way to manage resources like files, locks, and database transactions without leaking them.
How to think about it
A context manager is Python’s answer to the question: “how do I make sure cleanup always runs, even when things go wrong?” The with statement guarantees that __exit__ is called no matter what — normal exit, exception, even return in the middle of the block. That guarantee is what makes it the right tool for files, locks, database connections, and any other resource that must be released.
What’s really being tested
Interviewers want to see you explain the protocol (__enter__ / __exit__) and then show the contextlib.contextmanager shortcut — writing a full class is overkill for most real use cases.
Step 1 — The protocol
The with statement desugars to roughly:
ctx = ManagedResource()
value = ctx.__enter__() # runs setup; binds to `as` target
try:
body_runs_here()
except:
if not ctx.__exit__(*sys.exc_info()):
raise # re-raise unless __exit__ returns True
else:
ctx.__exit__(None, None, None)
Step 2 — The generator shortcut
contextlib.contextmanager turns a generator function into a context manager. Everything before yield is setup; everything after is teardown. finally ensures teardown runs even on exception.
Step 3 — Real-world patterns
Nested context managers
with open("in.txt") as src, open("out.txt", "w") as dst:
dst.write(src.read())
This is equivalent to nested with blocks — both are cleaned up in reverse order.