Home› Tutorials› Resource Management
Resource ManagementContext Managers in Python: with Statements and the Protocol Behind Them
- The
withstatement guarantees that cleanup code runs even if an exception occurs. - Any object implementing
__enter__and__exit__is a context manager. __exit__receives the exception info and can suppress it by returning a truthy value.contextlib.contextmanagerlets you write a manager as a generator function.
The setup and teardown problem
Many operations follow a three-phase pattern: acquire a resource, use it, release it. Files, database connections, locks, and network sockets all work this way. The release step must happen even if the usage step raises an exception, or you leak the resource.
Without a context manager, you need try/finally to guarantee cleanup:
lock = threading.Lock()
lock.acquire()
try:
shared_data.append(item)
finally:
lock.release() # must happen no matter what
This is correct but verbose. When every operation that touches a resource needs this boilerplate, the code becomes cluttered and the teardown logic scattered. Context managers consolidate the pattern.
The with statement
The with statement wraps a block of code with an object that handles setup and teardown:
with open("data.txt", encoding="utf-8") as fh:
content = fh.read()
# fh.close() was called here, exception or not
The optional as clause binds whatever __enter__ returns to a name. For a file, __enter__ returns the file object itself, so fh is both the context manager and the resource you work with.
You can enter multiple context managers in a single statement, which is cleaner than nesting:
with open("input.txt") as src, open("output.txt", "w") as dst:
dst.write(src.read())
The __enter__ and __exit__ protocol
Under the hood, with expr as name is equivalent to:
manager = expr
name = manager.__enter__()
try:
# body of the with block
finally:
manager.__exit__(exc_type, exc_val, exc_tb)
__enter__ is called on entry. Its return value is bound to the as target (if present).
__exit__ is always called on exit and receives three arguments: the exception type, value, and traceback (all None when no exception occurred). If __exit__ returns a truthy value, the exception is suppressed and execution continues after the with block. Returning None or False lets the exception propagate.
Writing a class-based context manager
Implement both dunder methods on a class to make it a context manager:
import time
class Timer:
def __enter__(self):
self._start = time.perf_counter()
return self # bound to "as" target
def __exit__(self, exc_type, exc_val, exc_tb):
self.elapsed = time.perf_counter() - self._start
return False # do not suppress exceptions
with Timer() as t:
expensive_operation()
print(f"Elapsed: {t.elapsed:.3f}s")
A context manager that suppresses a specific exception type:
class SuppressOSError:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return exc_type is OSError # True suppresses the exception
The contextlib shortcut
For simple managers that do not need to suppress exceptions, contextlib.contextmanager turns a generator function into a context manager. The code before yield is the setup; the code after is the teardown:
from contextlib import contextmanager
import tempfile, os
@contextmanager
def temp_directory():
path = tempfile.mkdtemp()
try:
yield path # body of the with block runs here
finally:
import shutil
shutil.rmtree(path) # always clean up
with temp_directory() as d:
with open(os.path.join(d, "scratch.txt"), "w") as fh:
fh.write("temporary data")
# directory is deleted here
contextlib also ships ready-made managers for common patterns: suppress(ExcType) silently ignores a specific exception; redirect_stdout(stream) redirects stdout within the block; ExitStack manages a dynamic number of context managers assembled at runtime.
Frequently asked questions
Does with guarantee teardown if I call sys.exit()?
sys.exit() raises SystemExit, which is an exception. __exit__ is called and runs teardown code unless the interpreter process itself is killed by a signal or power loss.Can I reuse a context manager?
threading.Lock can be entered and exited repeatedly. Check the documentation for the specific object.When should I write my own vs use an existing one?
tempfile.TemporaryDirectory, unittest.mock.patch), prefer the existing implementation.