Python Monkey Patching
Python 是一种典型的动态脚本语言,不仅支持动态类型,其对象模型本身也是动态的。Python 中的类是可变的,方法不过是类的一个属性,因此我们可以在运行时修改它们的行为。这种技巧被称为 Monkey Patching(猴子补丁),这个名字可能源于 “游击补丁”(Guerrilla patch),意思是“偷偷地修改代码”。
什么是 Monkey Patching?
简而言之,Monkey Patching 就是在运行时动态地替换对象的属性。在 Python 中,这通常表现为对函数、类或模块的动态修改。
我们来看一个简单的例子:
1 |
|
假设你不喜欢这个输出,因为你是个人类,于是你打算使用 Monkey Patch 改变它:
1 |
|
输出结果变成了“我是人类”,修改生效了,这就是 Monkey Patching 的效果。
可变与不可变类型的关系
理解 Monkey Patching,需要先搞清楚 Python 中 可变类型 与 不可变类型 的区别。
在 Python 中,变量更像是“标签”而不是“盒子”,它指向实际的对象。对于自定义对象(即类的实例),它们是可变的,因此可以直接修改其属性而不必新建对象。
一般来说:
- 不可变类型:
float
、decimal
、complex
、bool
、str
、tuple
、range
、frozenset
、bytes
等。 - 可变类型:
list
、dict
、set
、bytearray
、自定义类等。
Monkey Patching 正是利用了这种可变性。
前面的例子是修补了一个类的方法,因此该类的所有实例都会受到影响。但我们也可以只修补某个特定实例:
1 |
|
通过 types.MethodType
,我们仅改变了 monkey2
的方法实现。
修补模块
Monkey Patching 不仅限于类或实例,也可以应用到模块级别:
1 |
|
1 |
|
1 |
|
猴子通过猴子补丁“攻击”了人类模块。
警告 ⚠️
Monkey Patching 虽然强大,但也有风险。
它通常用于处理遗留系统或第三方库,我们希望在不直接修改源码的前提下改变行为,从而适配某些环境或修复 bug。但这种修改方式会让代码的行为变得难以追踪,容易造成混淆和维护困难。
比如,在上面的例子中,谁是猴子谁是人类就不那么容易分辨了。
因此,在可行的情况下,优先选择扩展库的公共接口,而不是直接打补丁。
测试中的应用
在测试中,Monkey Patching 是非常实用的。它可以临时替换依赖项,比如模拟一个方法的返回值,用于验证系统行为,而无需实际调用外部服务。
推荐库:Gorilla 🦍
我推荐使用 Gorilla,它让 Monkey Patching 变得更优雅、模块化,尤其是在需要对大量类或方法打补丁的场景中。
下面是一个例子:
1 |
|
上面的代码只是定义了补丁,尚未应用。我们可以使用 gorilla.find_patches
扫描并批量应用补丁:
1 |
|
这种方式不仅直观、干净,还大大提升了可维护性。
总结
Monkey Patching 是 Python 提供的一种强大但危险的能力。合理使用它,可以提升灵活性和适配能力,尤其适用于:
- 遗留代码的兼容性调整
- 单元测试中的方法模拟
- 第三方库的临时修复
但请始终保持谨慎,确保补丁行为明确、文档清晰,并在可能的情况下考虑更安全的替代方式。