什么是猴子修补?


Answers:


522

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

例如,考虑一个具有method的类get_data。该方法进行外部查找(例如,在数据库或Web API上),并且类中的各种其他方法都调用它。但是,在单元测试中,您不希望依赖于外部数据源-因此您可以用get_data返回一些固定数据的存根动态替换该方法。

因为Python类是可变的,并且方法只是类的属性,所以您可以随意执行此操作-实际上,您甚至可以以完全相同的方式替换模块中的类和函数。

但是,正如评论员指出的那样,在进行猴子修补时要格外小心:

  1. 如果除了测试逻辑调用之外还有其他要求get_data,它还会调用猴子修补的替代品,而不是原始替代品-可能是好是坏。提防。

  2. 如果存在某个变量或属性get_data,在您替换它时也指向该函数,则该别名将不会更改其含义,并且将继续指向原始get_data。(为什么?Python只是get_data将类中的名称重新绑定到其他函数对象;其他名称绑定完全不受影响。)


1
@LutzPrechelt对我来说很清楚,这是什么意思pointing to the original get_data function?您是说,如果有人将函数存储在变量中时,如果有人更改了该函数,该变量将继续指向旧函数?
fabriciorissetto

3
@fabriciorissetto:您通常不会在Python中更改函数对象。Monkey-patch时get_data,将名称重新绑定get_data到模拟函数。如果程序中其他位置的其他名称绑定到以前称为函数的功能get_data,则该其他名称将保持不变。
Lutz Prechelt

1
@LutzPrechelt您能解释一下吗?
加尔文·库

我认为,猴子修补尤其对调试以及装饰器或对象工厂函数有用。但是,请记住,显式比隐式要好,因此请确保您的代码对上下文不敏感,请阅读“ Goto认为是有害的”等……
aoeu256

因此,这类似于使用“ eval”功能,可以在运行时插入新代码?
wintermute

363

MonkeyPatch是一段Python代码,可在运行时(通常在启动时)扩展或修改其他代码。

一个简单的示例如下所示:

from SomeOtherProduct.SomeModule import SomeClass

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

SomeClass.speak = speak

资料来源: Zope Wiki上的MonkeyPatch页面。


126

什么是猴子补丁?

简而言之,猴子修补程序是在程序运行时对模块或类进行更改。

使用示例

在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但我想它以类似的方式使用了猴子修补程序。)


那么,monkeypatcher负担存储对真实方法的引用的负担吗?例如,如果忘记了“保留指针”步骤而丢失了该怎么办?
汤米

3
@Tommy如果对“覆盖”方法的引用为零-则将其垃圾回收,从而在进程的生命周期内“丢失”(或除非重新加载了它所在的模块,但通常不鼓励这样做)。
亚伦·霍尔

33

根据维基百科

在Python中,“猴子补丁”一词仅指在运行时对类或模块的动态修改,其目的是为了修补现有的第三方代码,以作为无法按需使用的错误或功能的解决方法。


16

首先:猴子修补是一个邪恶的hack(我认为)。

它通常用于用自定义实现替换模块或类级别的方法。

最常见的用例是在无法替换原始代码时为模块或类中的错误添加解决方法。在这种情况下,您可以通过猴子补丁替换“错误的”代码,并将其替换为您自己的模块/包中的实现。


8
万一有些模块猴子修补了同一件事:你注定要失败。
Andreas Jung

49
虽然它的力量一般来说确实确实很危险,但是它非常适合测试
dkrikun

1
最常见的用例实际上是用于测试,尤其是单元测试。您只想测试您的代码,因此可以修补任何外部调用以返回预期的结果。
brocoli

1
这不是邪恶的,我用它来修补其他人软件中的错误,直到发布新版本为止,而不是派生和创建新的依赖项。
nurettin

1
猴子修补可以通过“纯功能方式”完成,而不是可变的,“上下文敏感”,类似于goto的方式,只需在装饰器中进行修补即可,这些装饰器返回类/方法的新修补版本(而不是对其进行修改)。许多C#/ Java程序员都不了解REPL驱动的开发,因此他们在自己的IDE中进行编码,要求对所有内容进行静态定义。由于C#/ Java没有猴子补丁程序,因此当他们在JavaScript,Smalltalk,Lisp,Python等中看到它时,他们会认为它是邪恶的,因为这违背了其静态IDE驱动的开发实践。
aoeu256

13

猴子补丁只能用动态语言完成,其中python是一个很好的例子。在运行时更改方法而不是更新对象定义是一个示例;类似地,在运行时添加属性(无论方法还是变量)也被视为猴子补丁。这些操作通常在使用您没有来源的模块时完成,这样就无法轻易更改对象定义。

这被认为是不好的,因为这意味着对象的定义不能完全或准确地描述其实际行为。


但是,猴子修补很有用,只要您创建一个新版本的对象而不是修改现有的对象或类,而该对象的新版本在装饰器中的成员中进行了修补,则发出“嘿,我要修补您的消息”。
aoeu256

您可以在已修补成员上使用批注来将修补程序存储在已修补成员中的修补成员中。假设您有一个可撤消的装饰器,它使用undo方法创建函数对象的新的可撤消版本。您可以在装饰器中放置一个指向您不可撤消的装饰器的修补程序字段。
aoeu256

5

猴子修补程序是在运行时重新打开类中的现有类或方法并更改其行为,应谨慎使用它们,或者仅在确实需要时使用它。

由于Python是一种动态编程语言,因此类是可变的,因此您可以重新打开它们并进行修改甚至替换。


1

什么是猴子修补?猴子修补程序是一种用于在运行时动态更新一段代码的行为的技术。

为什么要使用猴子补丁?它允许我们在运行时修改或扩展库,模块,类或方法的行为,而无需实际修改源代码

结论猴子修补是一种很酷的技术,现在我们已经学习了如何在Python中进行修补。但是,正如我们所讨论的,它有其自身的缺点,应谨慎使用。

有关更多信息,请参考[1]:https : //medium.com/@nagillavenkatesh1234/monkey-patching-in-python-explained-with-examples-25eed0aea505

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.