自定义类型提示注解

Custom type hint annotation

我刚刚为 Python 编写了一个简单的 @autowired decorator,它根据类型注释实例化 classes。

为了启用 class 的惰性初始化,该包提供了一个 lazy(type_annotation: (Type, str)) 函数,以便调用者可以像这样使用它:

@autowired
def foo(bla, *, dep: lazy(MyClass)):
   ...

这很好用,在幕后这个 lazy 函数只是 returns 一个 returns 实际类型的函数,并且有一个 lazy_init 属性 设置为 True。此外,这 不会 破坏 IDE(例如,PyCharm)代码完成功能。

But I want to enable the use of a subscriptable Lazy type use instead of the lazy function.

像这样:

@autowired
def foo(bla, *, dep: Lazy[MyClass]):
   ...

这与 typing.Union 非常相似。 虽然我能够实现可订阅类型,但 IDE 的代码完成功能将变得无用,因为它会在 Lazy class 中提供属性建议, 不是 MyClass.

我一直在使用这段代码:

class LazyMetaclass(type):
    def __getitem__(lazy_type, type_annotation):
        return lazy_type(type_annotation)

class Lazy(metaclass=LazyMetaclass):
    def __init__(self, type_annotation):
        self.type_annotation = type_annotation

我尝试将 Lazy.__dict__ 重新定义为 属性 以转发到下标类型的 __dict__,但这似乎对 PyCharm 的代码完成功能没有影响。

我坚信我正在努力实现的目标是可能的,因为 typing.Union 与 IDE 的代码完成配合得很好。我一直在尝试破译 typing.Union 的源代码中的哪些内容使其在代码完成功能方面表现良好,但到目前为止没有成功。

要使 Container[Type] 符号起作用,您需要创建一个 user-defined generic type:

from typing import TypeVar, Generic

T = TypeVar('T')

class Lazy(Generic[T]):
    pass

然后你使用

def foo(bla, *, dep: Lazy[MyClass]):

Lazy 被看作是容纳 class 的容器。

注意:这个仍然意味着IDE将dep视为Lazy[=54类型的对象=]. Lazy这里是一个容器类型,持有一个MyClass类型的对象。您的 IDE 不会自动完成 MyClass 类型,您不能那样使用它。

该表示法也不会创建 Lazy class 的实例;它通过 GenericMeta metaclass 创建了一个 subclass。 subclass 有一个特殊的属性 __args__ 让你反省订阅参数:

>>> a = Lazy[str]
>>> issubclass(a, Lazy)
True
>>> a.__args__
(<class 'str'>,)

如果您只想在运行时访问类型注释,但懒惰地解析名称,您可以只支持字符串值:

def foo(bla, *, dep: 'MyClass'):

这是有效的类型注解,你的装饰器可以在运行时通过使用typing.get_type_hints() function解析名称(在延迟时间,而不是在装饰时),或者在修饰时将字符串包装在 lazy() 可调用对象中。

如果 lazy() 是为了标记一个类型以区别于其他类型提示,那么您正在尝试用其他含义重载类型提示注释,并简单地键入提示 不支持此类用例,并且使用 Lazy[...] 包含无法使其工作。