装饰类方法 - 如何将实例传递给装饰器?

80 投票
3 回答
46569 浏览
提问于 2025-04-15 19:55

这是Python 2.5版本的内容,而且它也和GAE有关,不过这并不重要。

我有以下这段代码。我在给bar类里的foo()方法加装饰器,使用的是dec_check这个类作为装饰器。

class dec_check(object):

  def __init__(self, f):
    self.func = f

  def __call__(self):
    print 'In dec_check.__init__()'
    self.func()

class bar(object):

  @dec_check
  def foo(self):
    print 'In bar.foo()'

b = bar()
b.foo()

当我执行这段代码时,我希望看到的是:

In dec_check.__init__()
In bar.foo()

但是我得到的错误是TypeError: foo() takes exactly 1 argument (0 given),因为.foo()是一个对象的方法,它需要一个self参数。我猜问题出在,当我执行装饰器代码时,bar的实例实际上并不存在。

那么我该如何将bar的实例传递给装饰器类呢?

3 个回答

12

如果你想把装饰器写成一个类,你可以这样做:

from functools import update_wrapper, partial

class MyDecorator(object):
    def __init__(self, func):
        update_wrapper(self, func)
        self.func = func

    def __get__(self, obj, objtype):
        """Support instance methods."""
        return partial(self.__call__, obj)

    def __call__(self, obj, *args, **kwargs):
        print('Logic here')
        return self.func(obj, *args, **kwargs)

my_decorator = MyDecorator

class MyClass(object):
     @my_decorator
     def my_method(self):
         pass
64

Alex的回答在函数足够用的时候是可以的。不过,当你需要一个类的时候,可以通过在装饰器类中添加以下方法来实现。

def __get__(self, obj, objtype):
    """Support instance methods."""
    import functools
    return functools.partial(self.__call__, obj)

要理解这个,你需要先了解描述符协议。描述符协议是将某个东西绑定到实例上的机制。它包括__get____set____delete__,这些方法在从实例的字典中获取、设置或删除某个东西时会被调用。

在这个情况下,当从实例中获取这个东西时,我们使用部分应用(partial)将它的__call__方法的第一个参数绑定到实例上。对于成员函数来说,这个过程在类构造时会自动完成,但对于像这样的合成成员函数,我们需要手动进行绑定。

102

你需要把装饰器变成一个描述符。这可以通过确保它的(元)类有一个__get__方法来实现,或者更简单的方法是使用装饰器函数,而不是装饰器(因为函数本身就是描述符)。比如:

def dec_check(f):
  def deco(self):
    print 'In deco'
    f(self)
  return deco

class bar(object):
  @dec_check
  def foo(self):
    print 'in bar.foo'

b = bar()
b.foo()

这会打印出

In deco
in bar.foo

你想要的结果。

撰写回答