# Python context managers **Python context managers** are objects that define `__enter__` and `__exit__` to manage setup and teardown around a `with` block. They guarantee cleanup runs even if an exception occurs, making them ideal for resource management (files, locks, database connections). Context managers eliminate try/finally boilerplate and make resource handling declarative and clear. ## Example This example implements a context manager for database connections and timing. ```python # run: python3 context_managers.py # description: custom context manager for resource management import time class DatabaseConnection: def __init__(self, name): self.name = name self.connected = False def __enter__(self): print(f"Connecting to {self.name}...") time.sleep(0.1) # simulate connection time self.connected = True return self def __exit__(self, exc_type, exc_val, exc_tb): print(f"Disconnecting from {self.name}...") self.connected = False if exc_type is not None: print(f"Exception occurred: {exc_type.__name__}: {exc_val}") return False # don't suppress exception def query(self, sql): if not self.connected: raise RuntimeError("Not connected") return f"Result of: {sql}" # Use context manager with DatabaseConnection("MyDB") as db: print(db.query("SELECT * FROM users")) print("Connection closed after with block\n") # Exception handling try: with DatabaseConnection("MyDB") as db: print(db.query("SELECT * FROM users")) raise ValueError("Bad query") except ValueError: print("Caught exception (cleanup still ran)") ``` ## Common patterns **Decorator approach** (simpler for simple cases): - `@contextlib.contextmanager` decorator on generator function - Yield the resource; code after yield runs on exit - Cleaner than writing a class with `__enter__` and `__exit__` **Return value from `__enter__`**: - Often `return self` to access the resource - Can return a different object - Assigned to the variable after `as` in `with` statement **`__exit__` parameters**: - `exc_type`: exception class (None if no exception) - `exc_val`: exception instance - `exc_tb`: exception traceback - Return `True` to suppress the exception; `False` to re-raise **Generator version**: ```python from contextlib import contextmanager @contextmanager def database(name): print(f"Connecting to {name}...") try: yield None # resource goes here finally: print(f"Disconnecting from {name}...") ``` **Nested contexts**: - Multiple `with` statements can nest: `with A() as a, B() as b:` - Or traditional nesting: `with A() as a: with B() as b:` - Resources acquired in order; cleaned up in reverse **Common uses**: - File handling (`with open(...)`) - Locks and mutexes (`with lock:`) - Database transactions (`with db.transaction():`) - Temporary directory changes (`with chdir(...)`) - Timing/profiling (`with timer():`) - Mocking and patching (`with patch(...):`)