什么是猴子补丁?
我想弄明白,什么是猴子补丁(monkey patching)或者说猴子补丁(monkey patch)?
这是不是类似于方法重载或者委托之类的东西?
它和这些东西有什么共同点吗?
8 个回答
什么是猴子补丁?
简单来说,猴子补丁就是在程序运行时对某个模块或类进行修改。
使用示例
在Pandas的文档中有一个猴子补丁的例子:
import pandas as pd
def just_foo_cols(self):
"""Get a list of column names containing the string 'foo'
"""
return [x for x in self.columns if 'foo' in x]
pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method
首先,我们需要导入我们的模块:
import pandas as pd
接下来,我们创建一个方法定义,这个方法是独立存在的,不属于任何类(因为在Python 3中,函数和独立方法的区别不大,所以不再区分):
def just_foo_cols(self):
"""Get a list of column names containing the string 'foo'
"""
return [x for x in self.columns if 'foo' in x]
然后,我们把这个方法附加到我们想要使用的类上:
pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
最后,我们可以在这个类的实例上使用这个方法,完成后再删除这个方法:
df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method
关于名称混淆的注意事项
如果你使用名称混淆(在属性前加双下划线,这会改变名称,我不推荐这样做),那么在进行猴子补丁时,你需要手动处理名称混淆。由于我不推荐使用名称混淆,这里就不演示了。
测试示例
那么我们可以如何利用这些知识进行测试呢?
假设我们需要模拟一次从外部数据源获取数据的调用,这次调用会出错,因为我们想确保在这种情况下程序的行为是正确的。我们可以通过猴子补丁来修改数据结构,以确保这种行为。(使用Daniel Roseman建议的类似方法名:)
import datasource
def get_data(self):
'''monkey patch datasource.Structure with this to simulate error'''
raise datasource.DataRetrievalError
datasource.Structure.get_data = get_data
当我们测试依赖于这个方法抛出错误的行为时,如果实现正确,我们将在测试结果中看到这种行为。
仅仅这样做会在整个进程中改变Structure
对象,所以在单元测试中你需要使用设置和拆解来避免这种情况,例如:
def setUp(self):
# retain a pointer to the actual real method:
self.real_get_data = datasource.Structure.get_data
# monkey patch it:
datasource.Structure.get_data = get_data
def tearDown(self):
# give the real method back to the Structure object:
datasource.Structure.get_data = self.real_get_data
(虽然上面的做法可以,但使用mock
库来补丁代码可能是个更好的主意。mock
的patch
装饰器比上述方法更不容易出错,因为它需要的代码行数更少,从而减少了引入错误的机会。我还没有查看mock
中的代码,但我想它可能以类似的方式使用猴子补丁。)
猴子补丁(MonkeyPatch)是一段Python代码,它可以在程序运行时(通常是在启动时)扩展或修改其他代码。
一个简单的例子是这样的:
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
来源: Zope wiki上的猴子补丁页面。
不,这跟那些东西都不一样。它只是指在运行时动态地替换属性。
举个例子,假设有一个类里面有个方法叫做 get_data
。这个方法会去外部查找数据(比如从数据库或者网络API获取),而这个类里的其他方法会调用它。但是,在单元测试的时候,你不想依赖外部的数据源——所以你可以动态地把 get_data
方法替换成一个返回固定数据的“桩”方法。
因为Python的类是可变的,而方法其实就是类的属性,所以你可以随意这样做——实际上,你甚至可以用同样的方式替换模块里的类和函数。
不过,正如一位评论者所指出的,使用这种“猴子补丁”技术时要小心:
如果除了你的测试逻辑之外,还有其他地方也调用了
get_data
,那么它们也会调用你替换后的版本,而不是原来的方法——这可能是好事,也可能是坏事。要小心。如果在你替换
get_data
之前,有某个变量或属性已经指向了这个函数,那么这个别名不会改变它的含义,仍然会指向原来的get_data
。这是因为Python只是把类里的get_data
名字重新绑定到了另一个函数对象上,其他的名字绑定不会受到影响。