Context Managers
Context managers provide a clean way to manage resources that need
setup and cleanup. Using the with statement ensures resources are
properly acquired and released, even if exceptions occur. This eliminates the need
for try/finally blocks and makes your code more readable and robust!
The with Statement
The with statement is Python's way of using context managers. When you
use with, Python automatically calls setup code (enter) before the
block and cleanup code (exit) after, even if an exception occurs. The most common
example is file handling!
Click Run to execute your code
1. The expression after
with must return a context manager2. Python calls
__enter__() before the block executes3. The return value of
__enter__() is assigned to the variable after
as4. The code block executes
5. Python calls
__exit__() after the block, even if an exception
occurred6.
__exit__() receives exception info if one occurred
The Context Manager Protocol
To create a custom context manager, you need a class that implements
__enter__() and __exit__() methods. The
__enter__ method runs setup code and optionally returns a value. The
__exit__ method handles cleanup.
Click Run to execute your code
__exit__ method receives three arguments
when an exception occurs: exception type, exception value, and traceback. Returning
True from __exit__ suppresses the exception (use
sparingly)!Multiple Context Managers
You can use multiple context managers in a single with statement,
either on separate lines or using commas. This is perfect for managing multiple
resources simultaneously!
Click Run to execute your code
Using @contextmanager
The contextlib module provides the @contextmanager
decorator, which lets you create context managers using generator functions. This is
often simpler than creating a full class!
Click Run to execute your code
@contextmanager, everything before
yield is setup code (like __enter__), and everything
after yield is cleanup code (like __exit__). If an
exception occurs, Python continues after yield to run cleanup, so use
try/finally if needed!
Common Mistakes
1. Not using with for resources
# Wrong - file might not close if exception occurs
file = open("data.txt")
content = file.read()
# What if exception happens here?
file.close() # Might not execute!
# Correct - use with statement
with open("data.txt") as file:
content = file.read()
# File automatically closed, even if exception occurs
2. Forgetting to handle exceptions in __exit__
# Wrong - cleanup might fail and hide original exception
class MyContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup() # If this raises, original exception lost!
# Correct - handle cleanup exceptions
class MyContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
try:
self.cleanup()
except Exception:
pass # Log but don't raise
return False # Don't suppress original exception
3. Returning True from __exit__ unintentionally
# Wrong - suppresses all exceptions!
class SuppressContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return True # Suppresses ALL exceptions!
with SuppressContext():
raise ValueError("This error is hidden!") # Won't propagate!
# Correct - only suppress specific exceptions
class SuppressContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type == ValueError: # Only suppress ValueError
return True # Suppress this specific exception
return False # Let others propagate
Exercise: Create a Timer Context Manager
Task: Create a custom context manager class that measures
execution time. Use __enter__ and __exit__ methods,
and print the elapsed time when exiting.
Requirements:
- Import the
timemodule - Create a
Timerclass with__enter__and__exit__methods - Record start time in
__enter__ - Calculate and print elapsed time in
__exit__ - Test it with the
withstatement and simulate some work
Click Run to execute your code
Show Solution
import time
class Timer:
"""Context manager for measuring execution time."""
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.time()
elapsed = self.end - self.start
print(f"Elapsed time: {elapsed:.4f} seconds")
return False # Don't suppress exceptions
# Test the Timer context manager
with Timer():
time.sleep(0.5) # Simulate some work
with Timer() as timer:
total = sum(range(1000000))
print(f"Sum result: {total}")
Summary
- Context Managers: Objects that manage resources with setup and cleanup code
- with Statement: Ensures proper resource management,
automatically calls
__enter__and__exit__ - __enter__: Setup method called before the block, return
value assigned to variable after
as - __exit__: Cleanup method called after the block, receives exception info if one occurred
- Exception Handling:
__exit__runs even if exceptions occur; returnTrueto suppress exceptions - @contextmanager: Decorator from
contextlibto create context managers from generator functions - Multiple Contexts: Use commas or separate lines in
withstatement for multiple resources - Common Uses: File handling, database connections, locks, temporary state changes, timing/debugging
What's Next?
Context managers are essential for proper resource management! Next, we'll explore regular expressions (regex), powerful pattern matching tools that let you search, extract, and manipulate text with complex patterns. Regex is invaluable for text processing, validation, and data extraction!
Enjoying these tutorials?