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

时间:2013-03-22 23:11:20

标签: python python-2.7 descriptor 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

尝试嵌套它们:

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没有。

问题:

  1. 我做错了什么?为什么我收到B().prop2错误?
  2. 什么是正确的方法?例如在重复使用object is not callableMyClassProperty(或MyClassMethodMyProperty
  3. 时定义classmethod的最佳方式是什么?

3 个答案:

答案 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__()