装饰一个方法

4 投票
2 回答
648 浏览
提问于 2025-04-16 02:16

在我的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 个回答

3

你代码的一部分是:

    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return func

这段代码定义了一个叫 wrapper 的东西,但完全没有用到它,而是返回了 func。很难判断这在你真实的代码中是否真的是个问题,因为显然你没有贴出完整的代码(比如你写错了 myEventManagremyEvnetManager 等),但如果你在实际代码中确实是这样做的,那这肯定是你问题的一部分。

4

这个装饰器的方法不奏效,因为装饰器是在类被创建的时候就被调用了,而不是在实例被创建的时候。当你这样写

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,那么你就只能使用你“旧式”的事件注册方法了。

简单来说:装饰一个方法实际上是装饰了类的未绑定方法,而你希望你的事件管理器接收到的是实例的绑定方法。

撰写回答