装饰方法

发布于 2021-01-29 16:42:35

在我的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则永远不会传递该参数。

在Internet上寻找解决方案后,我尝试过的另一种方法是使用类作为装饰器:

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__得到执行,而该事件的作品,尽管新的装饰。

什么是实现我想要的最佳方法,或者我只是缺少装饰器的一些重要概念?

关注者
0
被浏览
36
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    装饰器方法不起作用,因为装饰器是在构造类时调用的,而不是在构造实例时调用的。当你说

    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
    

    然后,listen.__get__当有人试图打电话将被称为f.some_method直接一些f......但你的计划的整点是,没有人的这样做!事件回调机制listen直接调用实例,这是因为它被传递了,并且listen实例正在调用它在创建时所释放的未绑定方法。listen.__get__永远不会被调用,并且_self永远不会正确设置参数…
    除非
    self.some_method像在上述__init__方法中那样显式访问自己。然后listen.__get__将在实例创建时被调用并_self进行正确设置。

    问题是(a)这是一个可怕的骇客,并且(b)如果您尝试创建两个实例,Foo则第二个实例将覆盖第一个实例_self,因为仍然只有一个listen对象正在创建,并且该对象与类,而不是实例。如果您只使用一个Foo实例,那很好,但是如果您必须让事件触发两个不同Foo的,则只需要使用“旧样式”事件注册即可。

    TL,DR版本:装饰方法装饰类的 未绑定 方法,而您希望事件管理器传递实例的 绑定 方法。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看