Site Tools


python-monkey-patching

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 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
python-monkey-patching.md · Last modified: by 127.0.0.1