解压 Python 的类型注解

Posted

技术标签:

【中文标题】解压 Python 的类型注解【英文标题】:Unpacking Python's Type Annotations 【发布时间】:2016-12-06 19:29:03 【问题描述】:

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

当类型是简单的内置类时,这部分可以正常工作:

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

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

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'

谢谢。

【问题讨论】:

【参考方案1】:

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。打字模块定义了这一点。请注意,这可能比您想要的更多,如果您使用它,请务必阅读_TypeAlias 文档。

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

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

【讨论】:

我发现它正在创建新实例,但不知何故我没有意识到== 是合适的(感觉有点傻) - 谢谢。你知道我应该如何解构组成类型吗?【参考方案2】:

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

【讨论】:

【参考方案3】:

注意,这适用于 Python 3.5.1

对于 Python 3.5.2,请查看 phillip 的回答。

您不应该像 Phillip 所说的那样检查身份运算符,使用相等来解决这个问题。

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

issubclass(List[int], list)  # True

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

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

signature.return_annotation.__parameters__[0] # int

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

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

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

【讨论】:

您使用哪个 Python 版本?至少对于 Python 3.5.2,没有 __parameters__ 属性;看看我的回答,那里是__args____forward_arg__ 也没有设置。 @Phillip Godamnit 它变化如此之快,不错。 感谢您的意见,吉姆。我已经给@Phillip 提供了答案,但我也使用了你的一些答案。我不相信堆栈溢出有一种方法可以分割声誉(如果有,请告诉我,我会为你切一些蛋糕)。感谢你们俩。你是如何找到这个问题的答案的?我无法在网上找到我需要的文档。您是否使用了源代码的文档? @freebie 将赏金交给 Phillip,他的回答解决了对 typing 模块所做的最新更改,只是不要立即授予它,等待赏金期结束(新的答案可能来吧,更多的人会看到这个问题,并且可能会觉得它很有趣)。至于如何,您可以查看它的来源,或者,我是如何做到的,启动 IPython 并内省对象。 @Jim 谢谢你的提示:)

以上是关于解压 Python 的类型注解的主要内容,如果未能解决你的问题,请参考以下文章

python类型注解

Python void 返回类型注解

Python3新特性 类型注解 以及 点点点

基于 Python 3 新增的函数注解(Function Annotations )语法实现参数类型检查功能

python 注解

Python类型注解:继承方法的返回类型