猴子补丁 Python 类

Posted

技术标签:

【中文标题】猴子补丁 Python 类【英文标题】:Monkey-patch Python class 【发布时间】:2011-04-15 11:30:40 【问题描述】:

我有一个类,位于一个单独的模块中,我无法更改。

from module import MyClass

class ReplaceClass(object)
  ...

MyClass = ReplaceClass

这不会改变 MyClass 除了这个文件之外的任何地方。但是,如果我要添加这样的方法

def bar():
   print 123

MyClass.foo = bar

这将起作用,并且 foo 方法将在其他任何地方可用。

如何完全替换类?

【问题讨论】:

【参考方案1】:
import module
class ReplaceClass(object):
    ....
module.MyClass = ReplaceClass

【讨论】:

+1 更快。只要确保它在任何使用 module.MyClass 的导入之前运行 更快并不意味着更正确,但我并不认为这需要额外的解释。一旦你知道如何做,区别应该是很明显的。 确保您是“第一个”导入课程。已经对“旧”类的引用不会被替换! 非常感谢! Ivo 指出这实际上是我的错误。我没有注意包含的顺序,这在重载或向类中添加方法时并不重要,因为它们仅在使用时才被调用。 这将仅影响此文件内还是全局更改行为【参考方案2】:

避免使用from ... import(可怕的;-) 方法来获取裸名,因为您最需要的是合格 名称。一旦你以正确的 Pythonic 方式做事:

import module

class ReplaceClass(object): ...

module.MyClass = ReplaceClass

这样,您就在修补 module 对象,这是您所需要的,并且在该模块用于其他人时将起作用。使用from ... 表单,您只是没有拥有 模块对象(一种看待大多数人使用from ... 的明显缺陷的方法),因此您的情况显然更糟; -);

我推荐使用from 语句的一种方法是从包中导入模块:

from some.package.here import amodule

所以你仍然会得到模块对象,并且会为该模块中的所有名称使用限定名称。

【讨论】:

+1 但请注意module.MyClass=,虽然它会替换模块中的类,但不会替换已经完成from module import MyClass 的任何其他模块中的类。他们将有自己的单独副本。猴子补丁很糟糕。 from module import 也很糟糕 mmkay;两者同时是出错的秘诀。 @bobince,绝对!如果代码的其他部分使用我讨厌的from module import MyClass,那么在“一团糟”和“不可能”之间找到和胡闹所有这些部分(@987654331 @ 可以有时帮助...但不是总是!-)。是的,它们都是不好的做法,但有时猴子补丁可能是最轻的,而使用 from 语句创建“人造裸名”可以总是省去。 我可以将这些字符串添加到 init.py 以确保首先发生这种替换吗?【参考方案3】:

我只是一个鸡蛋。 . . .也许对非新手来说很明显,但我需要from some.package.module import module 成语。

我不得不修改 GeneralHelpfulClass 的一种方法。这失败了:

import some.package.module

class SpeciallyHelpfulClass(some.package.module.GenerallyHelpfulClass): 
    def general_method(self):...

some.package.module.GenerallyHelpfulClass = SpeciallyHelpfulClass

代码运行,但没有使用重载到 SpeciallyHelpfulClass 的行为。

这行得通:

from some.package import module

class SpeciallyHelpfulClass(module.GenerallyHelpfulClass): 
    def general_method(self):...

module.GenerallyHelpfulClass = SpeciallyHelpfulClass

我推测 from ... import 成语“获取模块”,正如 Alex 所写,因为它将被包中的其他模块拾取。进一步推测,较长的虚线引用似乎将模块带入了通过长虚线引用导入的名称空间,但不会更改其他名称空间使用的模块。因此,对导入模块的更改只会出现在进行更改的名称空间中。就好像同一个模块有两个副本,每个副本在略有不同的引用下可用。

【讨论】:

+1 对此,因为我发现谈论扩展要修补的类然后替换它比仅仅扩展 Object 并替换它更有帮助。【参考方案4】:
import some_module_name

class MyClass(object): 
     ... #copy/paste source class and update/add your logic

some_module_name.MyClass = MyClass

替换时最好不要更改类的名称,因为不知何故有人可能使用 getattr 引用了它们 - 这将导致如下所示的失败

getattr(some_module_name, 'MyClass') --> 如果您将 MyClass 替换为 ReplaceClass ,则会失败!

【讨论】:

1) 这只会在执行导入的模块中生效:添加 as foo 无济于事。 2)您关于 getattr 的论点不仅令人困惑(因为您对名称的使用不一致)而且也是错误的,并且将类的名称与类本身混淆了。如果我 import somemodule; somemodule.someclass = someotherclass,那么稍后,在另一个模块中,getattr(somemodule, 'someclass') 将返回 someotherclass,这正是 OP 想要的。 3) 复制粘贴编程丑陋且容易出错。 @AaronMcSmooth,同意!,现在我已经编辑了答案,但是由于 OP 想要替换整个类,所以我编写了复制/粘贴和添加/更新逻辑,因为它不可能继承那个原始类,因为我们要替换它,如果 OP 只想更新几行逻辑,那么他需要从原始源获取其余代码! 现在您的解决方案与已经提出的两个解决方案相同,但添加了一个(仍然!)关于getattr 的错误陈述的缺陷另外,为什么我们不能从一个类继承我们要“替换”? class A(object):pass; class B(A): pass; A = B; a = A(). @shajapan:什么递归?这似乎没有问题。有什么我不知道的问题吗?

以上是关于猴子补丁 Python 类的主要内容,如果未能解决你的问题,请参考以下文章

python笔记69 - 什么是猴子补丁(Monkey Patch)?

猴子补丁(monkey patch)

Python之猴子补丁

Python 中的鸭子类型和猴子补丁

Python中的猴子补丁

python的猴子补丁monkey patch