In Python, Monkey Patching refers to dynamically modifying or extending behavior of a class or module at runtime. This allows developers to change how functions, methods or classes behave without altering original source code.
Monkey patching is a powerful feature, but it should be used carefully because it can lead to unexpected behavior if not managed properly.
For Example: Suppose we have a module monk.py with a simple class:
# monk.py
class A:
def func(self):
print("func() is being called")
Normally, calling func() would output:
func() is being called
For changing behavior at runtime we can replace func() method dynamically by assigning a new function to it:
import monk
# New function to replace original func()
def monkey_f(self):
print("monkey_f() is being called")
# Replacing the address of "func" with "monkey_f"
monk.A.func = monkey_f
obj = monk.A()
obj.func()
Output
monkey_f() is being called
Explanation:
- A new function monkey_f is defined.
- monkey_f is assigned to monk.A.func.
- All instances of class A now call monkey_f() instead of original func().
- This demonstrates Python’s ability to modify behavior dynamically at runtime.
Why Use Monkey Patching?
Monkey patching is useful in several scenarios:
- Fixing Bugs in Third-Party Modules: Methods can be patched without modifying original library.
- Testing and Mocking: Methods can be replaced during unit testing to simulate specific behaviors.
- Dynamic Behavior in Frameworks: Some frameworks, such as Django, use monkey patching internally to extend classes dynamically.
Selective Monkey Patching: Instance vs. Class
Monkey patching allows modifying the behavior of classes or their instances at runtime.
Example 1: This example shows how to patch a method for a single instance without affecting other instances of same class.
class B:
def greet(self):
print("Hello from B!")
# Create instances
b1 = B()
b2 = B()
# Define new method
def new_greet(self):
print("Hello from monkey patch!")
# Patch only b1
b1.greet = new_greet.__get__(b1) # Bind method to instance
b1.greet()
b2.greet()
Output
Hello from monkey patch! Hello from B!
Explanation:
- class B defines method greet and instances b1 and b2 are created.
- New function new_greet is defined and bound only to b1 using __get__.
- Calling b1.greet() executes patched method.
- Calling b2.greet() executes original method, demonstrating selective patching.
Example 2: This example shows how to patch square() for one instance so it acts as a cube calculator, while other instances keep original method.
class MathOps:
def square(self, x):
return x * x
# Create two instances
m1 = MathOps()
m2 = MathOps()
# Define a new method
def cube(self, x):
return x * x * x
# Patch only m1
m1.square = cube.__get__(m1) # Replace square with cube for m1
print(m1.square(3)) # Patched version → cube
print(m2.square(3)) # Original version → square
Output
27 9
Explanation:
- MathOps.square() normally returns square of a number.
- Two instances is created m1 and m2 and new method cube() is defined.
- Only m1 is patched, so m1.square(3) now returns cube (27).
- m2 keeps original behavior and returns square (9).
Note: Monkey patching can break other code and make maintenance difficult. Use it cautiously mainly for testing, quick fixes or prototyping.