【问题标题】: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 函数进行装饰?

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

【问题讨论】:

    标签: python class methods decorator python-decorators


    【解决方案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 sayinspect.ismethod 即使对于未绑定的方法也会返回 True。试试看!
    • @unutbu 未绑定方法也是方法,与函数不同。让f 成为一个函数。然后f.__get__(None, int) 返回一个未绑定的方法,而f.__get__(4) 返回一个绑定到对象4 的方法。
    • 我不明白为什么第二个例子返回 True,如果你说 Adder2.__call__ 返回一个未绑定的方法 - 它不应该返回 False 吗?
    猜你喜欢
    • 2014-01-14
    • 2020-01-11
    • 1970-01-01
    • 2014-12-07
    • 1970-01-01
    • 1970-01-01
    • 2016-07-04
    • 2015-12-08
    • 1970-01-01
    相关资源
    最近更新 更多