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

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

from module import MyClass

class ReplaceClass(object)
  ...

MyClass = ReplaceClass

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

def bar():
   print 123

MyClass.foo = bar

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

如何完全替换类?

【问题讨论】:

    标签: python monkeypatching


    【解决方案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:什么递归?这似乎没有问题。有什么我不知道的问题吗?
    猜你喜欢
    • 2012-12-18
    • 1970-01-01
    • 2017-10-05
    • 1970-01-01
    • 2012-08-19
    • 2016-09-01
    • 2012-09-16
    • 2020-01-09
    • 1970-01-01
    相关资源
    最近更新 更多