在Python中修补类的所有实例

时间:2016-05-08 18:40:20

标签: python monkeypatching

我使用的是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库中是否有任何内容会使这样的代码变得不必要,这已经实现了我正在做的事情,但更好?

4 个答案:

答案 0 :(得分:3)

在实例上查找时,动态动态创建方法;实例没有所有方法的副本,而descriptor protocol从类中获取函数,并根据需要将它们绑定到实例。这就是为什么monkeypatching这个类在这里工作;执行属性查找时,instance.method_name会找到mylib.MyClass.method_name

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

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

答案 1 :(得分:1)

你的方法似乎是最恐怖的方式。

Gevent,一个使用猴子补丁的流行图书馆performs monkey patching,几乎和你描述的一样。

答案 2 :(得分:0)

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

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()

答案 3 :(得分:0)

其中一张海报遗憾地删除了她/他的帖子,指示我走向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的第二个参数)。看起来更干净。