python-monkey-patching
Table of Contents
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.
# 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 methodClassName.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(notfrom 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 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 patchingpatch.object(obj, 'attr'): patch specific objectpatch.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
python-monkey-patching.md · Last modified: by 127.0.0.1
