解压 Python 的类型注解

Unpacking Python's Type Annotations

我正在尝试根据我在某些 Python 函数中提供的类型注释生成一些 JavaScript,方法是使用 inspect 中的 signature() 函数模块。

当类型是一个简单的内置函数时,这部分工作如我所料 class:

import inspect

def my_function() -> dict:
    pass

signature = inspect.signature(my_function)
signature.return_annotation is dict  # True

虽然我不确定如何打开和检查更复杂的注释,例如:

from typing import List
import inspect

def my_function() -> List[int]:
    pass

signature = inspect.signature(my_function)
signature.return_annotation is List[int]  # False

前向引用自定义时再次出现类似问题 class:

def my_function() -> List['User']:
    pass
...
signature.return_annotation  # typing.List[_ForwardRef('User')]

我想要得到的是这样的东西——所以我可以在生成 JavaScript:

时适当地分支
type = signature.return_annotation... # list
member_type = signature.return_annotation... # int / 'User'

谢谢。

List 不是 GenericMeta 的类型映射,尽管有语法。每次访问它都会生成一个新实例:

>>> [ id(List[str]) for i in range(3) ]
[33105112, 33106872, 33046936]

这意味着甚至List[int] is not List[int]。要比较两个实例,您有多种选择:

  • 使用==,即signature.return_annotation == List[int]
  • 将您的类型实例存储在全局变量中并进行检查,即

    a = List[int]
    def foo() -> a:
        pass
    inspect.signature(foo).return_annotation is a
    
  • 使用issubclass。 typing 模块定义了它。请注意,这可能比您想要的更多,如果您使用它,请务必阅读 _TypeAlias 文档。

  • 仅对照List检查并自己阅读内容。虽然 属性 是内部的,但实现不太可能很快改变:List[int].__args__[0] 包含从 Python 3.5.2 开始的类型参数,在早期版本中,它的 List[int].__parameters__[0].

如果您想为您的导出器编写通用代码,那么最后一个选项可能是最好的。如果您只需要涵盖特定用例,我个人会使用 ==.

请注意,这适用于 Python 3.5.1

对于Python 3.5.2 看看phillip的回答

您不应该像 Phillip 所说的那样使用身份运算符进行检查,使用相等来获得正确的结果。

要检查提示是否是 list 的子class,您可以使用 issubclass 检查(尽管您应该注意,在某些情况下这可能很古怪,并且目前正在处理):

issubclass(List[int], list)  # True

要获得类型提示的成员,您通常需要两个注意所涉及的情况。

如果它有一个简单类型,如 List[int] 参数的值位于 __parameters__ 值中:

signature.return_annotation.__parameters__[0] # int

现在,在更复杂的情况下,即作为参数提供的 class 与 List[User] 一起,您必须再次提取 __parameter__[0],然后获取 __forward_arg__。这是因为 Python 将参数包装在特殊的 ForwardRef class:

d = signature.return_annotation.__parameter__[0]
d.__forward_arg__ # 'User'

注意,你不需要在这里实际使用inspecttyping有一个名为get_type_hints的辅助函数returns 类型提示作为字典(它使用函数对象 __annotations__ 属性)。

Python 3.8为此提供了typing.get_origin()typing.get_args()

assert get_origin(Dict[str, int]) is dict
assert get_args(Dict[int, str]) == (int, str)

assert get_origin(Union[int, str]) is Union
assert get_args(Union[int, str]) == (int, str)

https://docs.python.org/3/library/typing.html#typing.get_origin