18

我无法理解为什么会发生以下情况。我有一个装饰器,它除了检查函数是否是方法之外什么都不做。我以为我已经了解 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 函数进行装饰?

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

4

2 回答 2

14

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

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

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

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

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

于 2012-09-19T10:52:29.190 回答
6

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

请注意,在 Python3 中, 的概念unbound method已被删除。那里,Adder2.__call__还有一个功能。

于 2012-09-19T10:51:23.860 回答