装饰器和类方法
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 吗?以上是关于装饰器和类方法的主要内容,如果未能解决你的问题,请参考以下文章