我很难理解当我尝试嵌套描述符/装饰器时会发生什么。我正在使用python 2.7。
例如,我们采用property
和classmethod
的以下简化版本:
class MyProperty(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, obj, objtype=None):
print 'IN MyProperty.__get__'
return self.fget(obj)
class MyClassMethod(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
print 'IN MyClassMethod.__get__'
def f(*args, **kwargs):
return self.f(objtype, *args, **kwargs)
return f
尝试嵌套它们:
class A(object):
# doesn't work:
@MyProperty
@MyClassMethod
def klsproperty(cls):
return 555
# works:
@MyProperty
def prop(self):
return 111
# works:
@MyClassMethod
def klsmethod(cls, x):
return x**2
% print A.klsproperty
IN MyProperty.__get__
...
TypeError: 'MyClassMethod' object is not callable
内部描述符__get__
的{{1}}方法未被调用。
我没有弄明白为什么,我试着投入(我认为)一个无操作的描述符:
MyClassMethod
尝试在嵌套中使用no-op描述符/装饰器:
class NoopDescriptor(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
print 'IN NoopDescriptor.__get__'
return self.f.__get__(obj, objtype=objtype)
我不明白为什么class B(object):
# works:
@NoopDescriptor
@MyProperty
def prop1(self):
return 888
# doesn't work:
@MyProperty
@NoopDescriptor
def prop2(self):
return 999
% print B().prop1
IN NoopDescriptor.__get__
IN MyProperty.__get__
888
% print B().prop2
IN MyProperty.__get__
...
TypeError: 'NoopDescriptor' object is not callable
有效,B().prop1
没有。
问题:
B().prop2
错误?object is not callable
和MyClassProperty
(或MyClassMethod
和MyProperty
)classmethod
的最佳方式是什么?
醇>
答案 0 :(得分:5)
在这种情况下,当使用没有参数的装饰器时,会使用它装饰的函数作为参数调用装饰器。使用装饰器的返回值代替装饰函数。所以:
@MyProperty
def prop(self):
...
相当于:
def prop(self):
...
prop = MyProperty(prop)
由于MyProperty
实现了描述符协议,访问A.prop
实际上会调用A.prop.__get__()
,并且您已定义__get__
来调用已装饰的对象(在这种情况下) ,原始的功能/方法),所以一切正常。
现在,在嵌套的情况下:
@MyProperty
@MyClassMethod
def prop(self):
...
等效于:
def prop(self):
...
prop = MyClassMethod(prop) # prop is now instance of MyClassMethod
prop = MyProperty(prop) # prop is now instance of MyProperty
# (with fget == MyClassMethod instance)
现在,与以前一样,访问A.prop
实际上会调用A.prop.__get__()
(MyProperty
},然后尝试调用 MyClassMethod
的实例(装饰并存储在fget
属性中的对象)。
但是MyClassMethod
没有定义__call__
方法,因此您收到错误MyClassMethod is not callable
。
要解决第二个问题:属性已经是类属性 - 在您的示例中,访问A.prop
将返回类对象中属性的值,A().prop
将返回值实例对象中的属性(如果实例未覆盖它,则可以与类对象相同)。
答案 1 :(得分:3)
如果使MyProperty
将描述符协议应用于其包装对象,则可以使代码正常工作:
class MyProperty(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, obj, objtype=None):
print('IN MyProperty.__get__')
try:
return self.fget.__get__(obj, objtype)()
except AttributeError: # self.fget has no __get__ method
return self.fget(obj)
现在您的示例代码可以运行:
class A(object):
@MyProperty
@MyClassMethod
def klsproperty(cls):
return 555
print(A.klsproperty)
输出结果为:
IN MyProperty.__get__
IN MyClassMethod.__get__
555
答案 2 :(得分:0)
我在格雷厄姆·杜普顿(Graham Dumpleton)的blog中找到了对我自己老问题的明确答案。
简而言之,我写的装饰器不尊重 descriptors protocol ,试图直接调用包装的函数/对象,而不是首先让他们有机会执行他们的"描述符魔术" (首先打电话给他们__get__()
。