用可调用类与普通函数装饰类方法为何表现不同?
问题:可调用类装饰器装饰类方法时实例调用报错的原因?
尝试为类方法添加装饰器时,用普通函数装饰一切正常,但用实现__call__的可调用类装饰时,类级调用正常,实例级调用抛出TypeError提示缺少self参数。最小复现代码如下:
import random class C(): """A simple class""" def __init__(self): self.x = random.random() def report(self): return 'The value is: '+str(self.x) class F(): """Simulate a function""" def __init__(self, f): self.f = f def __call__(self, *args, **kwargs): return self.f(*args, **kwargs) def decoratef(f): def f1(*args, **kwargs): return f(*args, **kwargs) return f1 def decorateF(f): def f1(*args, **kwargs): return f(*args, **kwargs) return F(f1) # This F is the only difference. report0 = C.report C.report = decoratef(report0) print('Test_f0:', C.report(C())) # Works. print('Test_f1:', C().report()) # Works. C.report = decorateF(report0) print('Test_F0:', C.report(C())) # Works. print('Test_F1:', C().report()) #TypeError: "C.report() missing 1 required positional argument: 'self'"
核心原因:Python方法绑定依赖描述符协议
Python中,类里定义的普通函数属于非数据描述符,它们自带实现了__get__方法。当实例访问类的函数属性时,__get__会自动把实例作为第一个参数(即self)绑定到函数上,生成绑定方法。比如C().report会变成绑定方法,调用时自动传入实例本身,无需手动传self。
而你定义的可调用类F只实现了__call__,没有实现描述符协议的__get__方法。当实例访问C().report时,不会触发自动绑定逻辑,返回的就是F的实例本身。调用这个实例时,__call__直接调用内部的f1,但f1原本是需要接收self参数的类方法,此时没有自动传入实例,自然会报错缺少self。
类级调用C.report(C())能正常工作,是因为你手动传入了实例作为第一个参数,相当于绕过了自动绑定步骤,和直接调用普通函数的逻辑一致。
如果要让可调用类装饰器支持实例方法绑定,只需给F添加__get__方法,示例如下:
class F(): """Simulate a function""" def __init__(self, f): self.f = f def __call__(self, *args, **kwargs): return self.f(*args, **kwargs) def __get__(self, instance, owner): # 实现绑定逻辑,返回绑定方法 return lambda *args, **kwargs: self.f(instance, *args, **kwargs)
这样C().report会通过__get__生成绑定了实例的方法,调用时就会自动传入self参数。
内容的提问来源于stack exchange,提问作者Kevin Kostlan
相关产品推荐
相关产品推荐

