The with Statement
with ensures the resource is properly closed/released regardless of what happens.
# Without context manager (bad — file may not close on error)
f = open("data.txt", "w")
f.write("Hello") # If this crashes, f.close() never runs!
f.close()
# With context manager (correct)
with open("data.txt", "w") as f:
f.write("Hello") # __exit__ calls f.close() automatically
# File is closed here — alwaysAlways use with for file operations. It is safer and more readable than manual open/close.
The Context Manager Protocol
Any class with __enter__ and __exit__ works as a context manager.
class Timer:
import time as _time
def __enter__(self):
import time
self.start = time.time()
return self # Bound to "as" variable
def __exit__(self, exc_type, exc_val, exc_tb):
import time
elapsed = time.time() - self.start
print(f"Elapsed: {elapsed:.4f}s")
return False # Don't suppress exceptions
with Timer() as t:
total = sum(range(1_000_000))contextlib.contextmanager – Generator Approach
Use @contextmanager to write context managers as generator functions — less boilerplate.
from contextlib import contextmanager
@contextmanager
def managed_resource(name):
print(f"Opening {name}")
try:
yield name # Everything before yield = __enter__
finally:
print(f"Closing {name}") # Always runs = __exit__
with managed_resource("database") as r:
print(f"Working with {r}")Multiple Context Managers
Combine multiple managers in one with statement.
# Open two files simultaneously
with open("input.txt", "r") as src, open("output.txt", "w") as dst:
for line in src:
dst.write(line.upper())
# Both files auto-closed after blockcontextlib.suppress – Ignore Specific Exceptions
Cleanly suppress known safe exceptions without try/except.
from contextlib import suppress
import os
# Instead of:
try:
os.remove("temp.txt")
except FileNotFoundError:
pass
# Use suppress:
with suppress(FileNotFoundError):
os.remove("temp.txt")Why with Guarantees Cleanup — Even When Code Crashes
A context manager runs setup on entry and teardown on exit no matter what — normal finish, early return, or an exception all trigger the cleanup. That's why with open(...) is safer than a bare open(): the file closes even if the code inside blows up.
# Without: a crash before close() leaks the file handle
f = open("data.txt")
process(f) # if this raises, f is never closed
f.close()
# With: __exit__ runs on the way out, exception or not
with open("data.txt") as f:
process(f) # file guaranteed closed afterward
Build your own two ways
# 1) class with __enter__ / __exit__
class Timer:
def __enter__(self): self.t = time.time(); return self
def __exit__(self, *exc): print(time.time() - self.t)
# 2) the shortcut: contextlib
from contextlib import contextmanager
@contextmanager
def timer():
t = time.time()
yield # everything before = enter, after = exit
print(time.time() - t)
__exit__ receives the exception info; return True from it to swallow the error, or None/False to let it propagate. Stack multiple: with A() as a, B() as b: closes them in reverse order.
🏋️ Practical Exercise
Work with the with statement:
- Open a file using
with open(...)and confirm it closes automatically. - Write a class implementing
__enter__and__exit__that prints on entry and exit. - Create the same context manager using
@contextlib.contextmanagerandyield. - Use
contextlib.suppressto ignore a specific exception.
🔥 Challenge Exercise
Build a Timer context manager that records how long the code inside its with block takes and prints the elapsed time on exit — implement it both as a class (__enter__/__exit__) and as a @contextmanager generator. Make sure the timer still reports correctly even if the block raises an exception, then use multiple context managers in one with line.
📋 Summary
- A context manager guarantees setup and cleanup around a block, even if an exception occurs.
- The
withstatement calls__enter__on entry and__exit__on exit. - Implement the protocol with
__enter__and__exit__methods on a class. - The
@contextlib.contextmanagerdecorator turns a generator (with oneyield) into a context manager. - Returning
Truefrom__exit__suppresses the exception; returning falsy lets it propagate. - You can manage several resources in one
withby separating them with commas.
Interview Questions on Context Managers
- What is a context manager and what problem does it solve?
- How does the
withstatement work under the hood? - What methods make up the context manager protocol?
- How do you create a context manager using
contextlib.contextmanager? - What does the return value of
__exit__control? - How do you manage multiple resources in one
withblock? - What does
contextlib.suppressdo?
Related Topics
FAQ
It guarantees cleanup. Resources like files, database connections, and locks are released automatically when the with block ends, even if an exception is raised — eliminating the need for manual try/finally.
Use the @contextlib.contextmanager decorator on a generator function: put setup code before a single yield and cleanup code after it. This is much shorter than writing a class with __enter__/__exit__.
True from __exit__ do? +It tells Python the exception raised inside the block has been handled and should be suppressed. Returning a falsy value (the default) lets the exception propagate normally.
with? +Yes: with open("a") as a, open("b") as b: manages both, closing each when the block ends. For a dynamic number of resources, use contextlib.ExitStack.

