7 min read
ā¢Question 22 of 41mediumContext Managers
Using with statement effectively.
What You'll Learn
- What context managers are and why they matter
- The with statement protocol
- Creating class-based context managers
- Using @contextmanager decorator
- Exception handling in context managers
Why Context Managers?
Context managers ensure resources are properly acquired and released, even when exceptions occur. They prevent resource leaks (file handles, database connections, locks).
code.pyPython
# Without context manager (problematic)
f = open("file.txt")
try:
data = f.read()
finally:
f.close() # Must remember to close!
# With context manager (safe and clean)
with open("file.txt") as f:
data = f.read()
# File automatically closed, even if exception occursThe Context Manager Protocol
A context manager implements __enter__ and __exit__:
code.pyPython
class ManagedResource:
def __init__(self, name):
self.name = name
print(f"Creating {name}")
def __enter__(self):
print(f"Entering {self.name}")
return self # Returned value is bound to 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Exiting {self.name}")
# exc_type, exc_val, exc_tb: exception info (or None)
# Return True to suppress exception, False to propagate
return False
with ManagedResource("MyResource") as resource:
print(f"Using {resource.name}")
# Output:
# Creating MyResource
# Entering MyResource
# Using MyResource
# Exiting MyResourcePractical Examples
Timer Context Manager
code.pyPython
import time
class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, *args):
self.elapsed = time.perf_counter() - self.start
print(f"Elapsed: {self.elapsed:.4f} seconds")
return False
with Timer() as t:
sum(range(1_000_000))
# Elapsed: 0.0312 seconds
# Access timing after block
print(f"Operation took {t.elapsed:.4f}s")Database Transaction Manager
code.pyPython
class Transaction:
def __init__(self, connection):
self.connection = connection
def __enter__(self):
self.connection.begin()
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.connection.commit()
print("Transaction committed")
else:
self.connection.rollback()
print(f"Transaction rolled back: {exc_val}")
return False # Don't suppress exceptions
# Usage
with Transaction(db_conn) as conn:
conn.execute("INSERT INTO users ...")
conn.execute("UPDATE accounts ...")
# Auto-commits on success, rolls back on exceptionUsing contextlib
The contextlib module provides utilities to simplify context managers:
@contextmanager Decorator
code.pyPython
from contextlib import contextmanager
@contextmanager
def timer():
start = time.perf_counter()
try:
yield # Code in 'with' block runs here
finally:
elapsed = time.perf_counter() - start
print(f"Elapsed: {elapsed:.4f}s")
with timer():
sum(range(1_000_000))
# With return value
@contextmanager
def temp_directory():
import tempfile
import shutil
path = tempfile.mkdtemp()
try:
yield path # Value after 'as'
finally:
shutil.rmtree(path)
with temp_directory() as tmpdir:
print(f"Working in {tmpdir}")
# Directory cleaned up automaticallyOther contextlib Utilities
code.pyPython
from contextlib import suppress, redirect_stdout, closing
import io
# suppress - ignore specific exceptions
with suppress(FileNotFoundError, PermissionError):
os.remove("maybe_missing.txt")
# No error if file doesn't exist
# redirect_stdout - capture print output
f = io.StringIO()
with redirect_stdout(f):
print("Hello, World!")
output = f.getvalue() # "Hello, World!\n"
# closing - call .close() on exit
from urllib.request import urlopen
with closing(urlopen("https://example.com")) as page:
content = page.read()Multiple Context Managers
code.pyPython
# Multiple in one line
with open("in.txt") as f_in, open("out.txt", "w") as f_out:
f_out.write(f_in.read())
# Parentheses for multiple lines (Python 3.10+)
with (
open("file1.txt") as f1,
open("file2.txt") as f2,
open("file3.txt") as f3
):
pass
# ExitStack for dynamic number
from contextlib import ExitStack
filenames = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
files = [stack.enter_context(open(f)) for f in filenames]
# All files properly closed on exitException Handling in exit
code.pyPython
class ExceptionHandler:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is ValueError:
print(f"Caught ValueError: {exc_val}")
return True # Suppress the exception
return False # Propagate other exceptions
with ExceptionHandler():
raise ValueError("This is suppressed")
print("Continues execution")
with ExceptionHandler():
raise TypeError("This propagates") # Will raise!Interview Tip
When asked about context managers:
- They manage setup/teardown with guaranteed cleanup
- enter acquires resources, exit releases them
- Use @contextmanager for simple cases
- exit returning True suppresses exceptions
- Common uses: files, locks, DB transactions, temp resources