如何使用装饰器获取 class 泛型的运行时类型?

How to get the runtime type of a class generic type by using a decorator?

我有一个通用的class喜欢

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 的构造函数调用,我什至通过使用 inspect.stack() 和 [=17 获得构造函数调用的代码行 f: Foo[int] = Foo() =] 以一种非常不漂亮的方式。然后我可以通过一些字符串操作得到 Foo[int]

除此之外,这是一种非常肮脏的方式,它只能获取字符串类型。但我需要实际类型。在此示例中,我可以使用 eval() 来“解析”字符串并将其转换为类型。但这不适用于自定义 classes,如本例所示:

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() 的正确上下文以“解析”字符串?

TL;DR:这不可能在 __init__ 中获取泛型的运行时类型,但我们可以在 CPython 实现中获得足够接近的

  1. 要仅使用 class 上的装饰器来处理此问题,您应该将调用更改为 h = Foo[List[Bar]](),以便装饰器可以独立于变量 holding 访问类型提示返回的对象。

  2. indicates that class instances of generic classes have a __orig_class__ attribute available after initialization. This attribute is set after init (see source code).

因此,如果我们编写一个 class 装饰器,装饰器应该修改源 class 以基本上在运行时设置 __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],)