6月4日 15:48

What is Context Manager in Python? How to use it?

Python Context Managers Explained

Basic Concepts of Context Managers

Context managers are objects in Python used to manage resources, defining operations that should be performed when entering and exiting a context. The most common usage is the with statement.

Why Context Managers are Needed

python
# Bad practice - Manual resource management file = open('example.txt', 'r') try: content = file.read() process(content) finally: file.close() # Good practice - Use context manager with open('example.txt', 'r') as file: content = file.read() process(content) # File automatically closed

Context Manager Protocol

Context managers need to implement two methods:

  • __enter__(self): Called when entering context
  • __exit__(self, exc_type, exc_val, exc_tb): Called when exiting context

Basic Implementation

python
class MyContextManager: def __init__(self, resource): self.resource = resource def __enter__(self): print("Entering context") return self.resource def __exit__(self, exc_type, exc_val, exc_tb): print("Exiting context") # Return True to suppress exception, False to propagate exception return False # Use context manager with MyContextManager("resource") as resource: print(f"Using resource: {resource}") # Output: # Entering context # Using resource: resource # Exiting context

Exception Handling

python
class SafeContextManager: def __enter__(self): print("Entering safe context") return self def __exit__(self, exc_type, exc_val, exc_tb): print("Exiting safe context") if exc_type is not None: print(f"Caught exception: {exc_type.__name__}: {exc_val}") # Return True to suppress exception return True return False # Test exception handling with SafeContextManager(): print("Executing operation") raise ValueError("Test exception") print("Program continues") # Output: # Entering safe context # Executing operation # Exiting safe context # Caught exception: ValueError: Test exception # Program continues

contextlib Module

@contextmanager Decorator

The @contextmanager decorator simplifies creating context managers.

python
from contextlib import contextmanager @contextmanager def simple_context(): print("Entering context") try: yield "resource" finally: print("Exiting context") # Use with simple_context() as resource: print(f"Using resource: {resource}")

Context Manager with Exception Handling

python
from contextlib import contextmanager @contextmanager def error_handling_context(): print("Entering error handling context") try: yield except ValueError as e: print(f"Handling ValueError: {e}") raise # Re-raise exception finally: print("Cleaning up resources") # Use try: with error_handling_context(): print("Executing operation") raise ValueError("Test exception") except ValueError: print("Caught re-raised exception")

closing Function

The closing function creates a context manager for objects without context manager protocol.

python
from contextlib import closing class Resource: def __init__(self, name): self.name = name def close(self): print(f"Closing resource: {self.name}") # Use closing with closing(Resource("database connection")) as resource: print(f"Using resource: {resource.name}") # Resource automatically closed

suppress Function

The suppress function is used to ignore specified exceptions.

python
from contextlib import suppress # Ignore FileNotFoundError with suppress(FileNotFoundError): with open('nonexistent.txt', 'r') as f: content = f.read() print("Program continues") # Ignore multiple exceptions with suppress(FileNotFoundError, PermissionError): with open('protected.txt', 'r') as f: content = f.read()

redirect_stdout and redirect_stderr

python
from contextlib import redirect_stdout, redirect_stderr import io # Redirect standard output output = io.StringIO() with redirect_stdout(output): print("This message is redirected") print(f"Captured output: {output.getvalue()}") # Redirect standard error error_output = io.StringIO() with redirect_stderr(error_output): print("Error message", file=sys.stderr) print(f"Captured error: {error_output.getvalue()}")

Practical Application Scenarios

1. File Operations

python
# Automatically close files with open('input.txt', 'r') as input_file: with open('output.txt', 'w') as output_file: for line in input_file: output_file.write(line.upper()) # Both files automatically closed

2. Database Connections

python
import sqlite3 from contextlib import contextmanager @contextmanager def database_connection(db_path): conn = sqlite3.connect(db_path) try: yield conn finally: conn.close() # Use with database_connection('example.db') as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM users") results = cursor.fetchall() # Connection automatically closed

3. Lock Management

python
import threading from contextlib import contextmanager class LockManager: def __init__(self): self.lock = threading.Lock() @contextmanager def acquire(self): self.lock.acquire() try: yield finally: self.lock.release() # Use lock_manager = LockManager() with lock_manager.acquire(): # Critical section code print("Executing critical section operation") # Lock automatically released

4. Temporary Directory

python
import tempfile import os # Create temporary directory with tempfile.TemporaryDirectory() as temp_dir: print(f"Temporary directory: {temp_dir}") temp_file = os.path.join(temp_dir, 'temp.txt') with open(temp_file, 'w') as f: f.write("Temporary data") # Temporary directory and contents automatically deleted on exit # Temporary directory has been deleted

5. Timer

python
import time from contextlib import contextmanager @contextmanager def timer(name): start_time = time.time() yield end_time = time.time() print(f"{name} took: {end_time - start_time:.4f} seconds") # Use with timer("Data processing"): data = [i ** 2 for i in range(1000000)] sum(data) # Output: Data processing took: 0.1234 seconds

6. Temporarily Change Configuration

python
from contextlib import contextmanager class Config: def __init__(self): self.debug = False self.verbose = False config = Config() @contextmanager def temporary_config(config_obj, **kwargs): """Temporarily modify configuration""" original_values = {} # Save original values for key, value in kwargs.items(): original_values[key] = getattr(config_obj, key) setattr(config_obj, key, value) try: yield finally: # Restore original values for key, value in original_values.items(): setattr(config_obj, key, value) # Use print(f"Debug mode: {config.debug}") # False with temporary_config(config, debug=True, verbose=True): print(f"Debug mode: {config.debug}") # True print(f"Verbose mode: {config.verbose}") # True print(f"Debug mode: {config.debug}") # False

7. Transaction Management

python
from contextlib import contextmanager class Database: def __init__(self): self.in_transaction = False self.data = {} def begin_transaction(self): self.in_transaction = True self.transaction_data = self.data.copy() def commit(self): self.in_transaction = False print("Transaction committed") def rollback(self): self.in_transaction = False self.data = self.transaction_data print("Transaction rolled back") @contextmanager def transaction(db): db.begin_transaction() try: yield db db.commit() except Exception as e: db.rollback() raise # Use db = Database() try: with transaction(db): db.data['key1'] = 'value1' db.data['key2'] = 'value2' # raise Exception("Test exception") # Will trigger rollback except Exception as e: print(f"Transaction failed: {e}") print(db.data)

Nested Context Managers

Using Multiple with Statements

python
# Nested usage with open('file1.txt', 'r') as f1: with open('file2.txt', 'r') as f2: content1 = f1.read() content2 = f2.read() # Process both files

Using contextlib.ExitStack

ExitStack allows dynamically managing multiple context managers.

python
from contextlib import ExitStack files = ['file1.txt', 'file2.txt', 'file3.txt'] with ExitStack() as stack: file_handles = [stack.enter_context(open(f, 'r')) for f in files] contents = [f.read() for f in file_handles] # All files automatically closed on exit

Conditional Context Managers

python
from contextlib import ExitStack, nullcontext def get_context(use_context): if use_context: return MyContextManager("resource") else: return nullcontext() # Conditionally use context manager with get_context(True) as resource: if resource is not None: print(f"Using resource: {resource}") else: print("Not using context manager")

Asynchronous Context Managers

Asynchronous Context Manager Protocol

Asynchronous context managers implement:

  • __aenter__(self): Asynchronously enter context
  • __aexit__(self, exc_type, exc_val, exc_tb): Asynchronously exit context
python
class AsyncContextManager: def __init__(self, resource): self.resource = resource async def __aenter__(self): print("Asynchronously entering context") await self.connect() return self.resource async def __aexit__(self, exc_type, exc_val, exc_tb): print("Asynchronously exiting context") await self.disconnect() async def connect(self): print("Connecting to resource...") async def disconnect(self): print("Disconnecting...") # Use asynchronous context manager async def use_async_context(): async with AsyncContextManager("async resource") as resource: print(f"Using resource: {resource}") # Run import asyncio asyncio.run(use_async_context())

asynccontextmanager Decorator

python
from contextlib import asynccontextmanager @asynccontextmanager async def async_resource(): print("Acquiring async resource") try: yield "async resource" finally: print("Releasing async resource") async def use_async_resource(): async with async_resource() as resource: print(f"Using resource: {resource}") asyncio.run(use_async_resource())

Best Practices

1. Ensure Resource Cleanup

python
# Good practice - Use finally to ensure cleanup class ResourceManager: def __enter__(self): self.resource = acquire_resource() return self.resource def __exit__(self, exc_type, exc_val, exc_tb): release_resource(self.resource) return False

2. Handle Exceptions Correctly

python
# Good practice - Distinguish exception types class SafeContextManager: def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: print("Normal exit") elif issubclass(exc_type, (ValueError, TypeError)): print(f"Handling expected exception: {exc_val}") return True # Suppress expected exceptions else: print(f"Unhandled exception: {exc_val}") return False # Propagate unhandled exceptions

3. Provide Useful Error Messages

python
class DatabaseContextManager: def __enter__(self): try: self.connection = connect_to_database() return self.connection except ConnectionError as e: raise RuntimeError(f"Cannot connect to database: {e}") def __exit__(self, exc_type, exc_val, exc_tb): if self.connection: try: self.connection.close() except Exception as e: print(f"Error closing connection: {e}")

4. Support Context Manager Chaining

python
class ChainedContextManager: def __init__(self, *managers): self.managers = managers def __enter__(self): self.entered_managers = [] try: for manager in self.managers: self.entered_managers.append(manager.__enter__()) return self.entered_managers[-1] except: # If failed, clean up entered contexts self.__exit__(None, None, None) raise def __exit__(self, exc_type, exc_val, exc_tb): # Exit contexts in reverse order for manager in reversed(self.entered_managers): manager.__exit__(exc_type, exc_val, exc_tb) return False

Summary

Core concepts of Python context managers:

  1. Basic Protocol: Implement __enter__ and __exit__ methods
  2. with Statement: Automatically manage resource entry and exit
  3. Exception Handling: Handle exceptions in __exit__
  4. contextlib Module: Simplify creating context managers
  5. Practical Applications: File operations, database connections, lock management, etc.
  6. Nested Management: Use multiple with statements or ExitStack
  7. Async Support: Asynchronous context managers for async code

Advantages of context managers:

  • Automatic resource management, avoid resource leaks
  • Cleaner, more readable code
  • Exception-safe, ensures resource cleanup
  • Support nesting and chaining

Mastering context managers enables writing safer and more elegant Python code.

标签:Python