在函数装饰器中调用Python实例方法

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

有没有什么简单的方法,让一个装饰器在类的实例被创建时,才调用这个类的一个实例方法呢?

class C:
    def instance_method(self):
      print('Method called')

    def decorator(f):
        print('Locals in decorator %s  ' % locals())
        def wrap(f):
            print('Locals in wrapper   %s' % locals())
            self.instance_method()
            return f
        return wrap

    @decorator
    def function(self):
      pass

c = C()
c.function()

我知道这样做不行,因为在调用decorator的时候,self是未定义的(因为它不是作为实例方法被调用的,类的引用在那时是不可用的)。于是我想出了这个解决方案:

class C:
    def instance_method(self):
      print('Method called')

    def decorator():
        print('Locals in decorator %s  ' % locals())
        def wrap(f):
            def wrapped_f(*args):
                print('Locals in wrapper   %s' % locals())
                args[0].instance_method()
                return f
            return wrapped_f
        return wrap

    @decorator()
    def function(self):
      pass

c = C()
c.function()

这个方法利用了一个事实:我知道任何实例方法的第一个参数都是self。不过,这个包装器的定义有个问题,就是每次执行这个函数时,实例方法都会被调用,这正是我不想要的。于是我又想出了一个稍微修改过的版本,这个版本可以工作:

class C:
    def instance_method(self):
      print('Method called')
def decorator(called=[]):
    print('Locals in decorator %s  ' % locals())
    def wrap(f):
        def wrapped_f(*args):
            print('Locals in wrapper   %s' % locals())
            if f.__name__ not in called:
                called.append(f.__name__)
                args[0].instance_method()
            return f
        return wrapped_f
    return wrap

@decorator()
def function(self):
  pass

c = C()
c.function()
c.function()

现在这个函数只会被调用一次,但我不喜欢每次调用这个函数时都要进行这个检查。我猜可能没有办法避免这种情况,但如果有人有建议,我很乐意听听!谢谢 :)

4 个回答

0

这可以通过使用可调用对象作为装饰器来实现。

class ADecorator(object):
    func = None
    def __new__(cls, func):
        dec = object.__new__(cls)
        dec.__init__(func)
        def wrapper(*args, **kw):
            return dec(*args, **kw)
        return wrapper

    def __init__(self, func, *args, **kw):
        self.func = func
        self.act  = self.do_first

    def do_rest(self, *args, **kw):
        pass

    def do_first(self, *args, **kw):
        args[0].a()
        self.act = self.do_rest

    def __call__(self, *args, **kw):
        return self.act(*args, **kw)

class A(object):
    def a(self):
        print "Original A.a()"

    @ADecorator
    def function(self):
        pass


a = A()
a.function()
a.function()
0

我觉得你问的问题有点根本上不可能实现。装饰器是在创建的时候就被创建了,但实例方法在实例存在之前是不存在的,也就是说,实例是后来的事情。所以,装饰器无法处理特定于实例的功能。

你可以把装饰器想象成一个函数转换器:它把一个函数变成另一个函数。但它并不关心这些函数的参数;它是在更高的层面上工作的。所以,在function的参数上调用实例方法并不是装饰器应该做的事情,而是function自己应该做的事情。

想要解决这个问题的方法有点像是变通。你的方法看起来还不错,算是个变通办法。

1

我想出了一个可能的替代方案。我喜欢这个方法,因为在定义函数的时候只调用了一次,在创建类的时候也只调用了一次。唯一的缺点就是这个方法会多占用一点点内存,用来存储函数的属性。

from types import FunctionType

class C:
    def __init__(self):
        for name,f in C.__dict__.iteritems():
            if type(f) == FunctionType and hasattr(f, 'setup'):
                  self.instance_method()

    def instance_method(self):
      print('Method called')

    def decorator(f):
        setattr(f, 'setup', True)
        return f

    @decorator
    def function(self):
      pass

c = C()
c.function()
c.function()

撰写回答