在描述符中,__call__可以访问使用装饰器的类吗?
考虑以下代码:
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