6

当我尝试嵌套描述符/装饰器时,我很难理解会发生什么。我正在使用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__内部描述符的方法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)

尝试在嵌套中使用无操作描述符/装饰器:

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无效。

问题:

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

3 回答 3

6

在这种情况下,当使用不带参数的装饰器时,会调用装饰器并将其装饰的函数作为其参数。使用装饰器的返回值而不是装饰函数。所以:

@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__()(in MyProperty),然后尝试调用MyClassMethod(被装饰并存储在fget属性中的对象)的实例。

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


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

于 2013-03-22T23:36:38.743 回答
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)

现在您的示例代码有效:

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

print(A.klsproperty)

输出是:

IN MyProperty.__get__
IN MyClassMethod.__get__
555
于 2013-03-23T00:53:50.297 回答
0

我在 Graham Dumpleton 的精彩博客中找到了我自己的老问题的明确答案。

简而言之,我编写的装饰器不尊重描述符协议,而是尝试直接调用包装的函数/对象,而不是首先给他们一个机会来执行他们的“描述符魔术”(通过调用他们的__get__()第一个)。

于 2014-08-01T20:18:01.787 回答