Context Managers
Ensure proper resource cleanup with context managers.
Context managers in Python are used to manage resources, ensuring they are properly acquired and released. They are typically used with the with statement and help prevent resource leaks.
Why Use Context Managers?
Section titled “Why Use Context Managers?”Without context managers (error-prone):
file = open('file.txt', 'r')try: content = file.read()finally: file.close() # Must remember to close!With context managers (pythonic):
with open('file.txt', 'r') as file: content = file.read()# File is automatically closed after the blockCreating Custom Context Managers
Section titled “Creating Custom Context Managers”Class-Based Context Manager
Section titled “Class-Based Context Manager”A context manager needs to implement __enter__ and __exit__ methods:
class DatabaseConnection: """Custom context manager for database connections""" def __init__(self, connection_string: str): self.connection_string = connection_string self.connection = None
def __enter__(self): """Called when entering the 'with' block""" print("Connecting to database...") self.connection = self.connect_to_database() return self.connection
def __exit__(self, exc_type, exc_value, traceback): """Called when exiting the 'with' block""" print("Closing database connection...") if self.connection: self.connection.close() return False # Don't suppress exceptions
def connect_to_database(self): # Simulate database connection return {"status": "connected"}
# Usagewith DatabaseConnection("postgresql://localhost/db") as db: print("Using database:", db) # Database operations here# Connection automatically closedFunction-Based Context Manager (using contextlib)
Section titled “Function-Based Context Manager (using contextlib)”The contextlib module provides a decorator for creating context managers:
from contextlib import contextmanager
@contextmanagerdef database_connection(connection_string: str): """Function-based context manager""" print("Connecting to database...") connection = connect_to_database(connection_string) try: yield connection # Value returned to 'as' variable finally: print("Closing database connection...") connection.close()
def connect_to_database(connection_string: str): return {"status": "connected"}
# Usagewith database_connection("postgresql://localhost/db") as db: print("Using database:", db)Real-World Examples
Section titled “Real-World Examples”Timer Context Manager
Section titled “Timer Context Manager”import timefrom contextlib import contextmanager
@contextmanagerdef timer(): """Context manager to measure execution time""" start = time.time() try: yield finally: end = time.time() print(f"Execution time: {end - start:.2f} seconds")
# Usagewith timer(): # Your code here time.sleep(1) print("Task completed")# Output: Execution time: 1.00 secondsTemporary Directory Context Manager
Section titled “Temporary Directory Context Manager”import tempfileimport shutilfrom contextlib import contextmanager
@contextmanagerdef temporary_directory(): """Create and clean up a temporary directory""" temp_dir = tempfile.mkdtemp() try: yield temp_dir finally: shutil.rmtree(temp_dir) # Clean up
# Usagewith temporary_directory() as temp_dir: # Work with temp_dir print(f"Working in {temp_dir}")# Directory automatically deletedLock Context Manager
Section titled “Lock Context Manager”import threadingfrom contextlib import contextmanager
@contextmanagerdef acquire_lock(lock: threading.Lock): """Acquire and release a lock""" lock.acquire() try: yield finally: lock.release()
# Usagelock = threading.Lock()with acquire_lock(lock): # Critical section print("Thread-safe code here")# Lock automatically releasedUnderstanding __exit__ Parameters
Section titled “Understanding __exit__ Parameters”The __exit__ method receives three parameters about any exception that occurred:
class ErrorLogger: """Context manager that logs exceptions""" def __enter__(self): return self
def __exit__(self, exc_type, exc_value, traceback): if exc_type is not None: print(f"Exception occurred: {exc_type.__name__}: {exc_value}") # Return False to propagate the exception # Return True to suppress it return False
# Usagewith ErrorLogger(): raise ValueError("Something went wrong")# Exception is logged but still raisedMultiple Context Managers
Section titled “Multiple Context Managers”You can use multiple context managers in one statement:
from contextlib import contextmanagerimport time
@contextmanagerdef timer(): start = time.time() try: yield finally: print(f"Time: {time.time() - start:.2f}s")
@contextmanagerdef logger(name: str): print(f"Starting {name}...") try: yield finally: print(f"Finished {name}")
# Multiple context managerswith timer(), logger("operation"): time.sleep(1) print("Doing work...")