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

Posted

技术标签:

【中文标题】在函数装饰器中调用 Python 实例方法【英文标题】:Calling Python instance methods in function decorators 【发布时间】:2011-03-23 05:59:36 【问题描述】:

是否有一种简洁的方法让装饰器仅在实例化类的实例时调用类的实例方法?

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

现在该函数只被调用一次,但我不喜欢每次调用该函数时都必须进行此检查的事实。我猜没有办法解决它,但如果有人有任何建议,我很想听听他们的意见!谢谢:)

【问题讨论】:

如果您使用 (AFAIK) python 2.5+,您可能应该子类化对象:class C(object): 以获得新型类的好处。 你的名字错了。你所说的decorator实际上是一个装饰器factory,你所说的wrap是装饰器。此外,使用called=[] 具有一定的欺骗性;即使在这种情况下它是正确的。 @katriealex 够公平的。我最近才熟悉装饰器的这种语法。我在另一个代码库中有这个示例,所以我只是快速重命名了这些函数以进行清理。感谢您的澄清:) 【参考方案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()

【讨论】:

向那些花时间制作回复的人道歉。非常感激。但是,我认为我最终提出的解决方案最适合我的需求。【参考方案2】:

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

另一种思考方式是装饰器是一个函子:它将函数转换为其他函数。但它没有说明这些函数的参数;它的工作水平比这更高。所以在function 的参数上调用实例方法不是装饰器应该做的事情;这是function应该做的事情。

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

【讨论】:

是的,我知道装饰器是如何定义的。我只是好奇之前是否有人遇到过这个问题,并且可能想出了比我更好的破解方法。【参考方案3】:

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

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

【讨论】:

这行得通,尽管每次执行装饰函数时仍会进行不必要的函数调用(至do_rest)。 我认为在一般情况下不可能同时消除检查和调用。但是,如果您知道方法名称,则可以将其重新绑定到无操作函数。方法名称可以从我们的装饰器接收到的func 对象中获取。所以你可以在 do_first() 中执行 args[0].__setattr__(self.func.__name__, do_nothing)。显然,您需要在某处声明一些 do_nothing 或为此使用一些内置函数。然而,这更加“骇人听闻”,而且节省的时间不值得可能的副作用。【参考方案4】:

C 类的多个实例应该如何表现?无论哪个实例调用function,是否应该只调用一次instance_method?还是每个实例都应该调用一次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

【讨论】:

是的。但是没有回答我的问题。 哦,您的意思是要删除if 语句?好吧,我知道一种方法,如果你希望instance_method 只被调用一次,无论什么实例调用c.function()。如果您希望C 的实例单独运行,我想不出没有if 语句的方法。 >如果您希望 C 的实例单独运行,我想不出没有 if 语句的方法——使用 f.__name__ 将其他方法重新绑定到实例部分实现了这一点(除非方法在被修饰后命名不同,或者在被调用之前在对象外部引用)

以上是关于在函数装饰器中调用 Python 实例方法的主要内容,如果未能解决你的问题,请参考以下文章

在装饰器中使用 ProcessPoolExecutor 酸洗 Python 函数失败

python_如何修改装饰器中参数?

python -- 装饰器

python装饰器中@wraps作用--修复被装饰后的函数名等属性的改变

如何将装饰器中的变量传递给装饰函数中的函数参数?

python包装函数在装饰器中接受参数