Python Monkey Patching

Python 是一种典型的动态脚本语言,不仅支持动态类型,其对象模型本身也是动态的。Python 中的类是可变的,方法不过是类的一个属性,因此我们可以在运行时修改它们的行为。这种技巧被称为 Monkey Patching(猴子补丁),这个名字可能源于 “游击补丁”(Guerrilla patch),意思是“偷偷地修改代码”。

什么是 Monkey Patching?

简而言之,Monkey Patching 就是在运行时动态地替换对象的属性。在 Python 中,这通常表现为对函数、类或模块的动态修改。

我们来看一个简单的例子:

1
2
3
4
# monkey.py
class Me:
def who_am_i(self):
print("I am a Monkey")

假设你不喜欢这个输出,因为你是个人类,于是你打算使用 Monkey Patch 改变它:

1
2
3
4
5
6
7
8
9
10
import monkey

def i_am_human(self):
print("I am human")

# 替换 who_am_i 方法
monkey.Me.who_am_i = i_am_human

obj = monkey.Me()
obj.who_am_i() # 输出:I am human

输出结果变成了“我是人类”,修改生效了,这就是 Monkey Patching 的效果。

可变与不可变类型的关系

理解 Monkey Patching,需要先搞清楚 Python 中 可变类型不可变类型 的区别。

在 Python 中,变量更像是“标签”而不是“盒子”,它指向实际的对象。对于自定义对象(即类的实例),它们是可变的,因此可以直接修改其属性而不必新建对象。

一般来说:

  • 不可变类型floatdecimalcomplexboolstrtuplerangefrozensetbytes 等。
  • 可变类型listdictsetbytearray、自定义类等。

Monkey Patching 正是利用了这种可变性。

前面的例子是修补了一个类的方法,因此该类的所有实例都会受到影响。但我们也可以只修补某个特定实例

1
2
3
4
5
6
7
8
9
10
11
12
13
import types
import monkey

monkey1 = monkey.Me()
monkey2 = monkey.Me()

def i_am_human(self):
print("I am human")

monkey2.who_am_i = types.MethodType(i_am_human, monkey2)

monkey1.who_am_i() # 输出:I am a Monkey
monkey2.who_am_i() # 输出:I am human

通过 types.MethodType,我们仅改变了 monkey2 的方法实现。

修补模块

Monkey Patching 不仅限于类或实例,也可以应用到模块级别:

1
2
3
# whoami.py
def who_am_i(monkey_type):
print(f"I am a {monkey_type}")
1
2
3
4
5
# human.py
import whoami

def who_am_i():
whoami.who_am_i("Human")
1
2
3
4
5
6
7
8
9
10
# monkey.py
import whoami
import human

def i_am_monkey(monkey_type):
print(f"I am a {monkey_type}")

whoami.who_am_i = i_am_monkey # 修补模块函数

human.who_am_i() # 输出:I am a Monkey

猴子通过猴子补丁“攻击”了人类模块。

警告 ⚠️

Monkey Patching 虽然强大,但也有风险。

它通常用于处理遗留系统或第三方库,我们希望在不直接修改源码的前提下改变行为,从而适配某些环境或修复 bug。但这种修改方式会让代码的行为变得难以追踪,容易造成混淆和维护困难。

比如,在上面的例子中,谁是猴子谁是人类就不那么容易分辨了。

因此,在可行的情况下,优先选择扩展库的公共接口,而不是直接打补丁。

测试中的应用

在测试中,Monkey Patching 是非常实用的。它可以临时替换依赖项,比如模拟一个方法的返回值,用于验证系统行为,而无需实际调用外部服务。

推荐库:Gorilla 🦍

我推荐使用 Gorilla,它让 Monkey Patching 变得更优雅、模块化,尤其是在需要对大量类或方法打补丁的场景中。

下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
import gorilla
import destination

@gorilla.patches(destination.Class)
class MyClass:
def method(self):
print("Hello")

@classmethod
def class_method(cls):
print("world!")

上面的代码只是定义了补丁,尚未应用。我们可以使用 gorilla.find_patches 扫描并批量应用补丁:

1
2
3
4
5
6
import gorilla
import mypackage

patches = gorilla.find_patches([mypackage])
for patch in patches:
gorilla.apply(patch)

这种方式不仅直观、干净,还大大提升了可维护性。

总结

Monkey Patching 是 Python 提供的一种强大但危险的能力。合理使用它,可以提升灵活性和适配能力,尤其适用于:

  • 遗留代码的兼容性调整
  • 单元测试中的方法模拟
  • 第三方库的临时修复

但请始终保持谨慎,确保补丁行为明确、文档清晰,并在可能的情况下考虑更安全的替代方式。


Python Monkey Patching
https://vegetablest.github.io/2025/05/31/python-monkey-patch/
作者
af su
发布于
2025年5月31日
许可协议