装饰器和类方法

Posted

技术标签:

【中文标题】装饰器和类方法【英文标题】:Decorators and class method 【发布时间】:2012-09-11 15:58:05 【问题描述】:

我无法理解为什么会发生以下情况。我有一个装饰器,它除了检查函数是否是方法之外什么都不做。我以为我已经理解了 Python 中的方法是什么,但显然情况并非如此:

import inspect

def deco(f):
    def g(*args):
        print inspect.ismethod(f)
        return f(*args)
    return g

class Adder:
    @deco
    def __call__(self, a):
        return a + 1

class Adder2:
    def __call__(self, a):
        return a + 2
Adder2.__call__ = deco(Adder2.__call__)

现在,运行以下命令:

>>> a = Adder()
>>> a(1)
False
2
>>> a2 = Adder2()
>>> a2(1)    
True
3

我希望这段代码打印两次 True。

那么,像在 Adder2 中那样手动装饰函数不完全等同于通过 @deco 函数进行装饰?

有人可以这么高兴并解释为什么会发生这种情况吗?

【问题讨论】:

【参考方案1】:

方法是与类相关联的函数。仅当您从已定义的类中检索方法时才会创建方法;方法是函数的包装器,同时也引用了类(也可以选择引用实例)。

第一种情况发生的是:Python 编译你的类定义Adder。它找到装饰器定义和一个函数。装饰器被传递给函数,返回一个 new 函数。该函数被添加到类定义中(存储在类__dict__ 中)。一直以来,您都在处理一个 python 函数,不是一个方法。这会在以后发生。

当您随后调用a(1) 时,查找显示该实例没有__call__Adder 类有,因此使用__getattribute__() 检索它。这会找到一个函数(你的deco装饰器),它是一个descriptor,所以它的__get__()方法被调用(所以Adder.__call__.__get__(a, Adder)),返回一个绑定方法 , 然后被调用并传入1 值。该方法被绑定,因为在调用__get__()instance 不是None。您的装饰器在类构建时包装了一个函数,打印False 因为它传递了一个展开的函数以开始。

然而,在第二种情况下,您检索一个方法(再次通过 __getattribute__() 在未修饰的 Adder2.__call__ 函数上调用 __get__()),这次未绑定(因为没有实例,只有一个类传递给 @987654341 @(完整的调用是 Adder2.__call__.__get__(None, Adder2)),然后然后装饰那个方法。现在 ismethod() 打印 True。

请注意,在 Python 3 中,后一种情况发生了变化。在 Python 3 中不再有未绑定方法的概念,只有函数和绑定方法。因此,“绑定”一词被完全删除。您的第二种情况也会打印False,因为Adder2.__call__.__get__(None, Adder2) 在这种情况下会返回一个函数。

【讨论】:

那么 Adder2.__call__ 返回一个绑定的方法?因为 Adder2.__call__.im_self == None 虽然 inspect.ismethod(Adder2.__call__) == True 。我发现here 绑定的方法是 f.im_self != None 的方法。你能解释一下吗? @JoeM.:不,我更正了答案。它返回一个未绑定的方法,因为您是从 Adder2 而不是从实例中检索它。 是的,但后来我不明白,为什么第二种情况下的 ismethod 也不打印 False - 因为 Adder2.__call__ 未绑定..(我不知道 inspect.ismethod 怎么样已定义,但我认为如果函数已绑定,它应该返回 True) @JoeM.: ismethod 为绑定和未绑定方法返回 True。 @JoeM.:啊,我明白了,这方面的文档具有误导性。下面,它只是对isinstance(object, types.MethodType) 的测试,它适用于绑定和未绑定的方法。【参考方案2】:

在类定义中,__call__ 是一个函数,而不是一个方法。通过属性查找(例如使用点语法)访问函数的行为,例如使用Adder2.__call__,会返回一个未绑定的方法。而a2.__call__ 返回一个绑定方法(self 绑定到a2)。

请注意,在 Python3 中,unbound method 的概念已被删除。在那里,Adder2.__call__ 也是一个函数。

【讨论】:

但是inspect.ismethod应该只针对绑定方法返回True,所以这里不解释第二种情况。 确实,在 Python 3 中,__get__instance 设置为 None 的函数上返回函数本身,而不是未绑定的方法。换句话说,方法总是绑定在 python 3 中,__get__ 仅在 instance 参数不是 None 时返回一个方法。 @JanneKarila:尽管the docs say,inspect.ismethod 即使对于未绑定的方法也会返回 True。试试看! @unutbu 未绑定方法也是方法,与函数不同。让f 成为一个函数。然后f.__get__(None, int) 返回一个未绑定的方法,而f.__get__(4) 返回一个绑定到对象4 的方法。 我不明白为什么第二个例子返回 True,如果你说 Adder2.__call__ 返回一个未绑定的方法 - 它不应该返回 False 吗?

以上是关于装饰器和类方法的主要内容,如果未能解决你的问题,请参考以下文章

函数装饰器和类装饰器实现单例类

乐字节Java反射之三:方法数组类加载器和类的生命周期

Python的静态方法和类方法

从装饰器传递位置参数时如何支持静态和类方法?

通过描述符和类装饰器,自定义staicmethod(静态方法)

ES6装饰器的使用