# Python monkey patching **Python monkey patching** is modifying modules, classes, or objects at runtime—replacing methods, adding attributes, or changing behavior without modifying source code. While powerful, it's a code smell that trades explicitness for flexibility; use it mainly for testing (with mocking), temporary workarounds, or plugin systems where modification is intended. Use monkey patching in tests to mock dependencies, or in frameworks to allow runtime customization. ## Example This example shows monkey patching for testing and runtime behavior changes. ```python # run: python3 monkey_patching.py # description: runtime modification of modules and classes # Original code class Database: def connect(self): return "Connected to real database" def query(self, sql): return f"Result: {sql}" # Monkey patch for testing def test_database(): original_connect = Database.connect # Replace method Database.connect = lambda self: "Mocked connection" Database.query = lambda self, sql: "Mocked result" db = Database() print(f"Test 1: {db.connect()}") print(f"Test 2: {db.query('SELECT *')}") # Restore original Database.connect = original_connect db = Database() print(f"After restore: {db.connect()}") test_database() print("\n" + "="*60 + "\n") # Monkey patch module import time original_sleep = time.sleep def fake_sleep(seconds): print(f"Would sleep {seconds}s (mocked)") time.sleep = fake_sleep print("With mock:") time.sleep(1) time.sleep = original_sleep print("After restore:") time.sleep(0.01) print("\n" + "="*60 + "\n") # Adding attributes/methods at runtime class User: def __init__(self, name): self.name = name # Add method User.greet = lambda self: f"Hello, {self.name}" # Add class attribute User.default_role = "user" user = User("Alice") print(f"Greeting: {user.greet()}") print(f"Role: {user.default_role}") # Monkey patch in subclass class Admin(User): pass Admin.greet = lambda self: f"Admin access: {self.name}" admin = Admin("Bob") print(f"Admin greeting: {admin.greet()}") print("\n" + "="*60 + "\n") # Using contextlib for scoped patches (Python 3.4+) from unittest.mock import patch class ApiClient: def get(self, url): return "Real response" def function_under_test(): client = ApiClient() return client.get("https://api.example.com") with patch.object(ApiClient, 'get', return_value="Mocked response"): print(f"In mock context: {function_under_test()}") print(f"Outside context: {function_under_test()}") ``` ## Common patterns **Replacing methods**: - `ClassName.method = new_method`: replace instance method - `ClassName.method = classmethod(func)`: replace with classmethod - All instances get new method **Replacing functions**: - `module.function = new_function`: replace module-level function - Affects all code that uses `module.function` (not `from module import function`) **Adding attributes/methods**: - `obj.attr = value`: add attribute to instance - `ClassName.attr = value`: add to class (affects all instances) - `ClassName.method = lambda self: ...`: add method **Saving and restoring**: - Always save original before patching - Restore in finally block or context manager - Otherwise tests interfere with each other **Testing with mocks**: - `unittest.mock.patch()`: context manager for scoped patching - `patch.object(obj, 'attr')`: patch specific object - `patch.multiple()`: patch multiple attributes - Automatic cleanup on exit **Common uses**: - Mocking external services in tests - Temporary workarounds for bugs - Plugin systems (allowing replacement) - Hot-patching in production (risky!) - Testing without real I/O **Gotchas**: - Affects global state; tests can interfere - Hard to debug ("where does this method come from?") - Breaks assumptions about class definition - Can hide bugs instead of fixing - Import timing matters: patch where it's used, not where defined **Avoiding monkey patching**: - Dependency injection (pass as parameter) - Strategy pattern (pass callable) - Composition over inheritance - Proper abstraction layers - Real test doubles (mocks, stubs) **When it's appropriate**: - Testing (with proper setup/teardown) - Frameworks extending functionality - Explicitly documented plugin systems - Temporary workarounds (with comment + ticket) - Never in production code