【问题标题】:Patching all instances of a class in Python在 Python 中修补类的所有实例
【发布时间】:2016-05-08 18:40:19
【问题描述】:

我使用的是 Python 3.5.1。 我写了一个库,在某些情况下(当文件直接运行时,即__name__ == '__main__')我想在其中一个类中装饰某些方法。它应该装饰所有可以创建的实例。我想以一种非侵入性的方式来做,即理想情况下,我的库中的类不需要任何特殊代码。

一段时间后,我设法实现了这样的东西,符合我的要求:

def patch(clazz, name, replacement):

    def wrap_original(orig):
        # when called with the original function, a new function will be returned
        # this new function, the wrapper, replaces the original function in the class
        # and when called it will call the provided replacement function with the
        # original function as first argument and the remaining arguments filled in by Python

        def wrapper(*args, **kwargs):
            return replacement(orig, *args, **kwargs)

        return wrapper

    orig = getattr(clazz, name)
    setattr(clazz, name, wrap_original(orig))

def replacement_function(orig, self, ... other argumnents ...):
    # orig here is the original function, so can be called like this:
    # orig(self, ... args ...)
    pass

patch(mylib.MyClass, 'method_name', replacemment_function)

令人惊讶的是,这段代码有效,虽然我还没有用类方法测试它,但我现在不需要它。它还修补在修补之前创建的实例,尽管我还不确定它是否好用;d

上面的代码可以说是困难的,在我写完之后我需要一段时间来思考它的工作方式,以便写解释注释。我想要更简单的东西。

问题:Python 库中是否有任何东西可以使这样的代码变得不必要,它已经实现了我正在做的事情,但更好?

【问题讨论】:

  • 这看起来类似于我所做的here。我也想不出更简单的东西。

标签: python monkeypatching


【解决方案1】:

在查找实例时动态地创建方法;实例没有所有方法的副本,而是 descriptor protocol 从类中获取函数并根据需要将这些函数绑定到实例。这就是猴子修补类在这里起作用的原因; instance.method_name 将在执行属性查找时找到mylib.MyClass.method_name

默认库中没有任何内容可以执行您在此处执行的操作,不,因为不同的代码可能需要不同的模式来处理对旧方法的委托。

您的方法看起来非常接近 how the Mercurial project 支持函数包装,因为原始被传递到包装器中。

【讨论】:

    【解决方案2】:

    您的方法似乎是最 Pythonic 的方式。

    Gevent,一个使用猴子补丁的流行库,performs monkey patching,几乎与您描述的方式相同。

    【讨论】:

      【解决方案3】:

      另一种选择是创建一个“null”装饰器函数,然后使用条件逻辑在该函数和“真实”装饰器之间切换:

      from decorator_lib import real_decorator
      
      def fake_decorator(fun):
          return fun
      
      if __name__ == '__main__':
          my_decorator = real_decorator
      else:
          my_decorator = fake_decorator
      
      
      # ... elsewhere in the module ...
      
      @my_decorator
      def method(self, a, b, c):
          pass
      
      # ... finally:
      if __name__ == '__main__':
          run_self_tests_or_whatever()
      

      【讨论】:

      • 这只有在你可以在装饰发生之前进行交换时才有效。如果类已经存在(并且有实例),正如问题所暗示的那样,事后更改装饰器不会解决任何问题。
      • OP 提到他想在作为 main 运行时装饰方法。在我看来,这是您可以提前处理的事情。
      • 这不需要我更改库代码吗?此外,执行修饰的库和文件是分开的,要修饰的类是在主代码运行之前定义的。我不认为这对我有用,或者我误解了你的建议。
      • 显然我误解了你原来的帖子。当你写我写了一个库,并且在某些情况下(当文件直接运行时,即__name__ == '__main__'),我理解这意味着你将直接从命令运行库模块行。
      【解决方案4】:

      这里的一位发帖人遗憾地删除了她/他的帖子,将我引向了functools 模块。最后,我选择了以下内容:

      def replacement(self, orig, ... other arguments ...):
          # orig here is the original function, so can be called like this:
          # orig(self, ... args ...)
          pass
      
      mylib.MyClass.my_method = functools.partialmethod(replacement, mylib.MyClass.my_method)
      

      origself 参数需要切换位置,因为partialmethod 将第一个参数绑定到它所在的实例,在这种情况下第二个将是原始函数(第二个参数到partialmethod)。看起来干净多了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-01
        • 2019-06-24
        • 2015-05-23
        • 1970-01-01
        • 2015-03-23
        • 2011-03-20
        • 1970-01-01
        • 2020-06-18
        相关资源
        最近更新 更多