在描述符中,__ call__是否可以访问使用装饰器的类?

时间:2011-12-08 23:04:39

标签: python decorator

请考虑以下代码:

class MyCustomDescriptor:
    def __init__(self,foo):
        self._foo = foo

    def __call__(self,decorated_method):
        # Here's my question...  Is there any way to get a reference to the
        # type (ClassA or ClassB) here?
        return self

    def __get__(self,instance,type):
        # Clearly at this point I can get the type of the class.
        # But it's too late, I would have liked
        # to get it back in __call__.
        return 10

class ClassA:
    @MyCustomDescriptor(foo=1)
    def some_value(self): pass

class ClassB:
    @MyCustomDescriptor(foo=1)
    def some_value(self): pass

我想要获得对类的引用的原因是我想使用修饰函数/方法向类中添加一些静态数据。我意识到这有些不典型,但对于我正在做的事情,它会有所帮助。

答案 - 无法完成。基于下面的响应之一,我从调用内部检查了堆栈,并且能够获得使用描述符的完全限定类(在我的示例中为ClassA或ClassB)。但是你不能将它变成一个类型/类,因为类型/类仍然被解析(或者在python中正确的术语)。换句话说,python遇到了ClassA并开始解析它。在解析它时,它会遇到描述符并调用init并调用描述符。 ClassA仍然没有完成解析。因此,无论您是否可以从调用中获取完全限定的模块/类名,您都无法将其转换为类型。

2 个答案:

答案 0 :(得分:4)

在应用装饰器时,some_value只是一个函数,而不是一个方法。所以,不,该函数无法知道它与特定类相关联。

有两种选择:

  • 将班级名称传递给MyCustomDescriptor(以及foo)或
  • 使用类装饰器创建描述符some_value

类装饰器看起来像这样:

def register(method_name,foo):
    def class_decorator(cls):
        method=getattr(cls,method_name)
        class MyCustomDescriptor(object):
            def __get__(self,instance,type):
                result=method(instance)
                return '{c}: {r}'.format(c=cls.__name__,r=result)
        setattr(cls,method_name,MyCustomDescriptor())
        return cls
    return class_decorator

@register('some_value',foo=1)
class ClassA:
    def some_value(self):
        return 10

例如,运行

a=ClassA()
print(a.some_value)

产量

ClassA: 10

答案 1 :(得分:1)

嗯......有一种我能想到的方法,但它有资格作为我喜欢称之为“Python voodoo”的东西,这意味着它正在访问不应该用于普通编程的Python特性。所以在你这样做之前仔细考虑。它也是依赖于实现的,所以如果你希望你的代码可以移植到其他Python解释器(CPython除外),请不要依赖它。话虽如此:

当调用描述符的__call__方法时,您可以使用inspect.stack()访问解释器堆栈。返回列表中的第二个堆栈帧表示调用__call__的上下文。该堆栈框架中包含的部分信息是上下文名称,通常是函数名称,但在这种情况下,__call__未从函数内部调用,它是从类内部调用的,因此上下文name将是该类的名称。

import inspect

class MyCustomDescriptor:
    def __call__(self,decorated_method):
        self.caller_name = inspect.stack()[1][3]
        return self
    ...