我有一个通用类,例如
from typing import Generic, TypeVar, List
T = TypeVar('T')
@my_decorator
class Foo(Generic[T]):
pass
f: Foo[int] = Foo()
g: Foo[List[float]] = Foo()
是否有一种干净的方法来获取装饰器中构造函数调用Foo[int], Foo[List[float]]
的类型注释?我想在运行时进行类型检查。
我可以通过装饰器访问Foo
的构造函数调用,甚至可以使用f: Foo[int] = Foo()
和inspect.stack()
获得构造函数调用的代码行inspect.getsource(my_frame)
以一种非常不漂亮的方式然后我可以通过一些字符串操作来获得Foo[int]
。
除了这是一种非常肮脏的方式之外,它仅以字符串形式获取类型。但是我需要实际的类型。在此示例中,我可以使用eval()
“解析”字符串并将其转换为类型。但这不适用于自定义类,例如以下示例:
from typing import Generic, TypeVar, List
T = TypeVar('T')
class Bar:
pass
@my_decorator
class Foo(Generic[T]):
pass
h: Foo[List[Bar]] = Foo()
在这种情况下,我无法使用eval()
,因为我不知道如何获得正确的context
。
我想得到类似my_file.Foo[typing.List[my_file.Bar]]
这样的东西,它可以让我在运行时进行类型检查。
那么有什么干净的方法吗?还是至少有一种(肮脏的)方式为eval()
获取正确的上下文来“解析”字符串?
答案 0 :(得分:1)
TL; DR:不可能在__init__
中获得泛型的运行时类型,但是我们可以在CPython实现中获得足够的距离
要在类上仅使用装饰器来处理此问题,应将调用更改为h = Foo[List[Bar]]()
,以便装饰器可以独立于保存返回对象的变量来访问typehint。
This answer表示通用类的类实例具有在{em> 初始化后可用的__orig_class__
属性。此属性是在 初始化之后设置的(请参见source code)。
因此,如果我们编写类装饰器,则装饰器应修改源类以基本上在运行时设置__orig_class__
属性时进行侦听。但是,这在很大程度上依赖于一个未记录的实现细节,并且在将来的Python版本或其他实现中可能无法以相同的方式工作。
def my_decorator(cls):
orig_bases = cls.__orig_bases__
genericType = orig_bases[0]
class cls2(cls, genericType):
def __init__(self, *args, **kwargs):
super(cls2, self).__init__(*args, *kwargs)
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
if name == "__orig_class__":
print("Runtime generic type is " + str(get_args(self.__orig_class__)))
cls2.__orig_bases__ = orig_bases
return cls2
然后:
>>> h = Foo[List[Bar]]()
Runtime generic type is (typing.List[__main__.Bar],)