在python中嵌套描述符/装饰器

shx*_*hx2 6 python descriptor python-2.7 python-decorators

我很难理解当我尝试嵌套描述符/装饰器时会发生什么.我正在使用python 2.7.

例如,让我们的以下简化版本propertyclassmethod:

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
Run Code Online (Sandbox Code Playgroud)

试图嵌套它们:

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
Run Code Online (Sandbox Code Playgroud)

__get__内描述的方法MyClassMethod是没有得到调用.没有弄明白为什么,我试着投入(我认为)一个无操作描述符:

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)
Run Code Online (Sandbox Code Playgroud)

尝试在嵌套中使用no-op描述符/装饰器:

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
Run Code Online (Sandbox Code Playgroud)

我不明白为什么B().prop1有效而B().prop2不是.

问题:

  1. 我究竟做错了什么?为什么我收到object is not callable错误?
  2. 什么是正确的方法?例如,MyClassProperty在重复使用MyClassMethodMyProperty(classmethodproperty)时定义的最佳方式是什么

ise*_*dev 6

在这种情况下,当使用没有参数的装饰器时,使用它装饰的函数作为参数调用装饰器.使用装饰器的返回值代替装饰函数.所以:

@MyProperty
def prop(self):
    ...
Run Code Online (Sandbox Code Playgroud)

相当于:

def prop(self):
    ...
prop = MyProperty(prop)
Run Code Online (Sandbox Code Playgroud)

由于MyProperty实现了描述符协议,访问A.prop实际上会调用A.prop.__get__(),并且你已经定义__get__了调用被装饰的对象(在本例中是原始函数/方法),所以一切正常.

现在,在嵌套的情况下:

@MyProperty
@MyClassMethod
def prop(self):
    ...
Run Code Online (Sandbox Code Playgroud)

相当于:

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)
Run Code Online (Sandbox Code Playgroud)

现在,和以前一样,访问A.prop实际上会调用A.prop.__get__()(in MyProperty)然后尝试调用MyClassMethod(被装饰并存储在fget属性中的对象)的实例.

但是MyClassMethod没有__call__定义方法,所以你得到了错误MyClassMethod is not callable.


并解决第二个问题:属性已经是一个类属性 - 在您的示例中,访问A.prop将返回类对象中A().prop属性的值,并将返回实例对象中的属性值(可以与如果实例没有覆盖它,则为类对象).


Blc*_*ght 4

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)
Run Code Online (Sandbox Code Playgroud)

现在您的示例代码可以运行:

class A(object):
    @MyProperty
    @MyClassMethod
    def klsproperty(cls):
        return 555

print(A.klsproperty)
Run Code Online (Sandbox Code Playgroud)

输出是:

IN MyProperty.__get__
IN MyClassMethod.__get__
555
Run Code Online (Sandbox Code Playgroud)