装饰一个方法
在我的Python应用程序中,我使用事件来让不同的插件之间进行交流。现在,我想用装饰器来自动注册这些事件的方法,而不是手动去注册。
我希望能做到像这样:
@events.listento('event.name')
def myClassMethod(self, event):
...
我最开始尝试这样做:
def listento(to):
def listen_(func):
myEventManager.listen(to, func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return func
return listen_
当我在实例内部调用myEventManger.listen('event', self.method)
时,一切都运行得很好。但是,如果我使用装饰器的方法,self
这个参数就没有被传递。
我在网上找解决方案时,尝试了另一种方法,就是用一个类作为装饰器:
class listen(object):
def __init__(self, method):
myEventManager.listen('frontend.route.register', self)
self._method = method
self._name = method.__name__
self._self = None
def __get__(self, instance, owner):
self._self = instance
return self
def __call__(self, *args, **kwargs):
return self._method(self._self, *args, **kwargs)
这个方法的问题在于,我不太理解__get__
这个概念,而且我不知道该如何把参数放进去。为了测试,我尝试使用一个固定的事件来监听,但用这种方法什么都没有发生。当我添加打印语句时,我可以看到__init__
被调用了。如果我再加一个“老式”的事件注册,__get__
和__call__
都会被执行,事件也能正常工作,尽管有新的装饰器。
那么,达到我想要的效果的最佳方法是什么呢?还是说我只是对装饰器的一些重要概念理解得不够?
2 个回答
你代码的一部分是:
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return func
这段代码定义了一个叫 wrapper
的东西,但完全没有用到它,而是返回了 func
。很难判断这在你真实的代码中是否真的是个问题,因为显然你没有贴出完整的代码(比如你写错了 myEventManagre
和 myEvnetManager
等),但如果你在实际代码中确实是这样做的,那这肯定是你问题的一部分。
这个装饰器的方法不奏效,因为装饰器是在类被创建的时候就被调用了,而不是在实例被创建的时候。当你这样写
class Foo(object):
@some_decorator
def bar(self, *args, **kwargs):
# etc etc
时,some_decorator
会在类 Foo 被创建时被调用,并且它接收到的是一个未绑定的方法,而不是某个实例的绑定方法。这就是为什么self
没有被传递的原因。
而第二种方法,只要你每个使用装饰器的类只创建一个对象,并且你稍微聪明一点,就可以工作。如果你像上面那样定义listen
,然后再定义
class Foo(object):
def __init__(self, *args, **kwargs):
self.some_method = self.some_method # SEE BELOW FOR EXPLANATION
# etc etc
@listen
def some_method(self, *args, **kwargs):
# etc etc
那么当有人直接调用f.some_method
时,listen.__get__
会被调用...但你这个方案的重点就是没人会这样做!事件回调机制直接调用listen
实例,因为那就是它接收到的,而listen
实例会调用它在创建时保存的未绑定方法。listen.__get__
永远不会被调用,_self
参数也不会被正确设置...除非你自己明确地访问self.some_method
,就像我在上面的__init__
方法中做的那样。这样的话,listen.__get__
会在实例创建时被调用,_self
会被正确设置。
问题是(a)这是一种非常糟糕的黑科技,(b)如果你尝试创建两个Foo
的实例,那么第二个实例会覆盖第一个实例设置的_self
,因为仍然只有一个listen
对象被创建,它是与类关联的,而不是与实例关联的。如果你只使用一个Foo
实例,那就没问题,但如果你必须让事件触发两个不同的Foo
,那么你就只能使用你“旧式”的事件注册方法了。
简单来说:装饰一个方法实际上是装饰了类的未绑定方法,而你希望你的事件管理器接收到的是实例的绑定方法。