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

时间:2010-07-30 13:06:36

标签: python methods decorator instance

只有在实例化类的实例时,是否有一种干净的方法让装饰器在类上调用实例方法?

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()

我知道这不起作用,因为self未被定义decorator被调用(因为它没有被调用为实例方法,因为没有对该类的可用引用)。然后我提出了这个解决方案:

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 :(得分: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()

答案 1 :(得分:0)

我认为你在问一些根本不可能的事情。装饰器将与同时创建,但实例方法在实例执行之前不存在,稍后才会存在。因此,装饰器无法处理特定于实例的功能。

另一种思考方式是装饰器是一个仿函数:它将函数转换为其他函数。但它没有说明这些函数的参数;它的工作水平高于此水平。因此,在function的参数上调用实例方法不应该由装饰器完成;这应该由function完成。

解决这个问题的方法必然是hackish。你的方法看起来不错,就像黑客一样。

答案 2 :(得分:0)

可以通过使用callables作为装饰器来实现。

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()

答案 3 :(得分:0)

C的多个实例应如何表现? instance_method只应调用一次,无论哪个实例调用function?或者每个实例应该调用一次instance_method吗?

您的called=[]默认参数使装饰器记住调用了字符串名function的内容。如果decorator用于两个具有名为function的方法的不同类,该怎么办?然后

c=C()
d=D()
c.function()
d.function()

只会调用c.instance_method并阻止调用d.instance_method。很奇怪,可能不是你想要的。

下面,我使用self._instance_method_called来记录是否已调用self.instance_method。这会使C的每个实例最多调用instance_method一次。

如果您希望instance_method最多被调用一次,而不管C调用function的哪个实例,那么只需将_instance_method_called定义为类属性而不是实例属性。

def decorator():
    print('Locals in decorator %s  ' % locals())
    def wrap(f):
        def wrapped(self,*args):
            print('Locals in wrapper   %s' % locals())            
            if not self._instance_method_called:
                self.instance_method()
                self._instance_method_called=True
            return f
        return wrapped
    return wrap

class C:
    def __init__(self):
        self._instance_method_called=False
    def instance_method(self): print('Method called')
    @decorator()
    def function(self):
      pass

c = C()
# Locals in decorator {}  
c.function()
# Locals in wrapper   {'self': <__main__.C instance at 0xb76f1aec>, 'args': (), 'f': <function function at 0xb76eed14>}
# Method called
c.function()
# Locals in wrapper   {'self': <__main__.C instance at 0xb76f1aec>, 'args': (), 'f': <function function at 0xb76eed14>}

d = C()
d.function()
# Locals in wrapper   {'self': <__main__.C instance at 0xb76f1bcc>, 'args': (), 'f': <function function at 0xb76eed14>}
# Method called
d.function()
# Locals in wrapper   {'self': <__main__.C instance at 0xb76f1bcc>, 'args': (), 'f': <function function at 0xb76eed14>}

编辑:删除if声明:

def decorator():
    print('Locals in decorator %s  ' % locals())
    def wrap(f):
        def rest(self,*args):
            print('Locals in wrapper   %s' % locals())
            return f
        def first(self,*args):
            print('Locals in wrapper   %s' % locals())            
            self.instance_method()
            setattr(self.__class__,f.func_name,rest)
            return f
        return first
    return wrap

class C:
    def instance_method(self): print('Method called')
    @decorator()
    def function(self):
      pass