我使用的是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库中是否有任何内容会使这样的代码变得不必要,这已经实现了我正在做的事情,但更好?
答案 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)
切换位置所需的orig
和self
参数,因为partialmethod
将第一个参数绑定到它所在的实例,而第二个参数在这种情况下将是原始函数(partialmethod
的第二个参数)。看起来更干净。