什么是猴子补丁?

857 投票
8 回答
361446 浏览
提问于 2025-04-16 15:31

我想弄明白,什么是猴子补丁(monkey patching)或者说猴子补丁(monkey patch)?

这是不是类似于方法重载或者委托之类的东西?

它和这些东西有什么共同点吗?

8 个回答

174

什么是猴子补丁?

简单来说,猴子补丁就是在程序运行时对某个模块或类进行修改。

使用示例

在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库来补丁代码可能是个更好的主意。mockpatch装饰器比上述方法更不容易出错,因为它需要的代码行数更少,从而减少了引入错误的机会。我还没有查看mock中的代码,但我想它可能以类似的方式使用猴子补丁。)

467

猴子补丁(MonkeyPatch)是一段Python代码,它可以在程序运行时(通常是在启动时)扩展或修改其他代码。

一个简单的例子是这样的:

from SomeOtherProduct.SomeModule import SomeClass

def speak(self):
    return "ook ook eee eee eee!"

SomeClass.speak = speak

来源: Zope wiki上的猴子补丁页面。

771

不,这跟那些东西都不一样。它只是指在运行时动态地替换属性。

举个例子,假设有一个类里面有个方法叫做 get_data。这个方法会去外部查找数据(比如从数据库或者网络API获取),而这个类里的其他方法会调用它。但是,在单元测试的时候,你不想依赖外部的数据源——所以你可以动态地把 get_data 方法替换成一个返回固定数据的“桩”方法。

因为Python的类是可变的,而方法其实就是类的属性,所以你可以随意这样做——实际上,你甚至可以用同样的方式替换模块里的类和函数。

不过,正如一位评论者所指出的,使用这种“猴子补丁”技术时要小心:

  1. 如果除了你的测试逻辑之外,还有其他地方也调用了 get_data,那么它们也会调用你替换后的版本,而不是原来的方法——这可能是好事,也可能是坏事。要小心。

  2. 如果在你替换 get_data 之前,有某个变量或属性已经指向了这个函数,那么这个别名不会改变它的含义,仍然会指向原来的 get_data。这是因为Python只是把类里的 get_data 名字重新绑定到了另一个函数对象上,其他的名字绑定不会受到影响。

撰写回答