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.
This example shows monkey patching for testing and runtime behavior changes.
# 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()}")
Replacing methods:
ClassName.method = new_method: replace instance methodClassName.method = classmethod(func): replace with classmethodReplacing functions:
module.function = new_function: replace module-level functionmodule.function (not from module import function)Adding attributes/methods:
obj.attr = value: add attribute to instanceClassName.attr = value: add to class (affects all instances)ClassName.method = lambda self: ...: add methodSaving and restoring:
Testing with mocks:
unittest.mock.patch(): context manager for scoped patchingpatch.object(obj, 'attr'): patch specific objectpatch.multiple(): patch multiple attributesCommon uses:
Gotchas:
Avoiding monkey patching:
When it's appropriate: