【问题标题】:Decorating a method装饰方法
【发布时间】:2011-03-25 00:07: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)时,一切都运行良好。但是,如果我使用装饰器方法,selfargument 永远不会通过。

我在网上搜索解决方案后尝试的另一种方法是使用类作为装饰器:

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__get 都被执行,尽管有新的装饰器,该事件仍然有效。

什么是实现我正在寻找的最佳方式,或者我只是错过了装饰器的一些重要概念?

【问题讨论】:

  • 您确定在定义方法后只调用myEventManager.listen('frontend.route.register', myClassMethod) 不会更开心吗?由于您根本没有修改方法的行为,我不确定装饰器是否最适合这里。
  • 我希望直接从方法定义中看到哪个方法注册到什么事件。

标签: python methods decorator


【解决方案1】:

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

class Foo(object):
  @some_decorator
  def bar(self, *args, **kwargs):
    # etc etc

然后some_decorator会在类Foo被构造的时候被调用,它会被传递一个unbound方法,而不是实例的绑定方法。这就是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 直接调用f.some_method 时,将调用listen.__get__...但是您的计划的重点是没有人这样做!事件回调机制直接调用listen 实例,因为这就是它被传递的内容,而listen 实例正在调用它在创建时被隐藏的未绑定方法。 listen.__get__ 永远不会被调用,_self 参数也永远不会被正确设置...除非您自己显式访问 self.some_method,就像我在上面的 __init__ 方法中所做的那样。然后listen.__get__会在实例创建时被调用,_self会被正确设置。

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

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

【讨论】:

  • 啊我想我现在明白了。非常感谢您的详细解释!所以没有呃,让我们称之为正确的方法,我在寻找什么?
  • 我不知道。你发现的“类作为带有__get__ 的装饰器”技巧装饰了一个方法,如果你只以f.some_method() 访问该方法,但这种技术有局限性,你第一次尝试时碰巧发现了其中一个.
【解决方案2】:

您的部分代码是:

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

它定义了wrapper,然后完全忽略它并返回func。很难说这在你的真实代码中是否是一个真正的问题,因为显然你没有发布它(正如myEventManagremyEvnetManager、&c 等拼写错误所证明的那样),但如果这就是你在实际代码中所做的编码它显然是你的问题的一部分。

【讨论】:

  • 我刚刚为事件注册发明了一些东西,所以很清楚是什么意思:) 抱歉,写得很草率,很早......无论如何,你是对的,这是一个实际的我没有发现的错误(包装器与函数)。然而,最终它并没有什么不同,因为我根本不想改变方法,所以我也可以完全省略包装器。
猜你喜欢
  • 2020-01-11
  • 2014-01-14
  • 1970-01-01
  • 1970-01-01
  • 2012-07-22
  • 2013-05-23
  • 1970-01-01
  • 1970-01-01
  • 2011-08-25
相关资源
最近更新 更多