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:

Replacing functions:

Adding attributes/methods:

Saving and restoring:

Testing with mocks:

Common uses:

Gotchas:

Avoiding monkey patching:

When it's appropriate: