在描述符中,__call__可以访问使用装饰器的类吗?

3 投票
2 回答
649 浏览
提问于 2025-04-17 08:00

考虑以下代码:

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和call方法。这时ClassA还没有完全解析完。因此,尽管你可以从调用中获取到完整的模块/类名,但你无法把它转变为一个类型。

2 个回答

1

嗯……我想到了一种方法,但这可以称为“Python巫术”,意思是它使用了Python的一些不应该在正常编程中使用的特性。所以在使用之前请仔细考虑。这也和具体的实现有关,所以如果你希望你的代码能在其他Python解释器上运行(除了CPython),就不要依赖这个方法。话虽如此:

当描述符的__call__方法被调用时,你可以通过inspect.stack()来访问解释器的调用栈。返回列表中的第二个栈帧代表了__call__被调用时的上下文。这个栈帧中包含的一部分信息是上下文名称,通常是一个函数名,但在这种情况下,__call__并不是在一个函数内部被调用,而是在一个类内部被调用,所以上下文名称将是类的名称。

import inspect

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

在装饰器被应用的那一刻,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

撰写回答