Python is a remarkably flexible language when it comes to defining attributes.
Because of this, Python users may encounter the term monkey patching, which refers to dynamically extending or modifying an object at runtime — for example, by adding new data or method attributes, or by assigning a new callable object to an existing method name.
Naturally, this only works if the object in question allows attributes to be created or reassigned.
In the example below, we use monkey patching to extend the behavior of a class object, and later of an instance created from it, by adding new methods at runtime.
We start by importing a class from a module. However, the set of methods it provides is not quite enough for our purposes, so we assign an additional one directly to the class.
As a result, every instance created afterward will automatically have access to this new method.
Later, for some reason—such as performing a temporary calculation during testing—we need yet another new method, but only for a specific instance.
To achieve this, we define a regular function, just as we would inside the class body. Then, we assign it to an appropriately named attribute of the instance.
However, since the first parameter of the function (self) must refer to the instance, we actually assign a partial function where the first argument is fixed to the instance object.
Because this new method is associated only with that particular instance, other instances of the same class will not have this capability.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# Module: geometry class Rectangle: def __init__(self, a, b): self.a, self.b = a, b def perimeter(self): return 2 * (self.a + self.b) def area(self): return self.a * self.b |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
from math import atan, degrees from functools import partial from geometry import Rectangle # TEST # Defining a new method for the class object at runtime. Rectangle.diagonal = lambda self: pow(self.a ** 2 + self.b ** 2, 0.5) # This is monkey patching. # Instantiation rectangle = Rectangle(3, 4) # Using the existing methods on the instance. print(rectangle.perimeter()) # Result: 14 print(rectangle.area()) # Result: 12 # Using the new method added at runtime. print(rectangle.diagonal()) # Result: 5.0 # Defining and assigning a new method specific to this instance. def _diagonal_angles(self): alpha = degrees(atan(self.a / self.b)) beta = 90 - alpha return alpha, beta rectangle.diagonal_angles = partial(_diagonal_angles, rectangle) # This is monkey patching. # Using the instance-specific new method. angle1, angle2 = rectangle.diagonal_angles() print(f'{angle1:.2f}{chr(0x00B0)} {angle2:.2f}{chr(0x00B0)}') # Result: 36.87° 53.13° |
As for the etymology, the expression “monkey patching” originally comes from “guerrilla patching.”
Guerrilla warfare is a nontraditional, somewhat stealthy style of combat that’s difficult to track from the outside. This metaphor fits well with the idea of dynamically modifying program behavior at runtime.
Over time, the word guerrilla was humorously confused with gorilla, and from there softened into monkey — hence the term monkey patching.
Beyond the etymology of the word, the important question is whether it is worth using this technique, and if so, when?
The answer: you can use it — it’s not forbidden — but it should be used sparingly and with caution.
In certain special cases (for example, quick testing or temporary debugging), monkey patching can be a handy tool.
However, in code that’s meant to be reused or shared with others, it’s not recommended, since it can make the code harder to understand and increase the risk of unexpected behavior or subtle bugs.
With proper design and the use of suitable language constructs, there should be no need for monkey patching in a well-structured, production-ready codebase.
More details about the language features and constructs used in this post can be found in the e-book Python Knowledge Building Step by Step.