解压 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'
注意,这里不需要实际使用inspect
,typing
有一个名为get_type_hints
的辅助函数,它以字典的形式返回类型提示(它使用函数对象__annotations__
属性)。
【讨论】:
您使用哪个 Python 版本?至少对于 Python 3.5.2,没有__parameters__
属性;看看我的回答,那里是__args__
。 __forward_arg__
也没有设置。
@Phillip Godamnit 它变化如此之快,不错。
感谢您的意见,吉姆。我已经给@Phillip 提供了答案,但我也使用了你的一些答案。我不相信堆栈溢出有一种方法可以分割声誉(如果有,请告诉我,我会为你切一些蛋糕)。感谢你们俩。你是如何找到这个问题的答案的?我无法在网上找到我需要的文档。您是否使用了源代码的文档?
@freebie 将赏金交给 Phillip,他的回答解决了对 typing
模块所做的最新更改,只是不要立即授予它,等待赏金期结束(新的答案可能来吧,更多的人会看到这个问题,并且可能会觉得它很有趣)。至于如何,您可以查看它的来源,或者,我是如何做到的,启动 IPython
并内省对象。
@Jim 谢谢你的提示:)以上是关于解压 Python 的类型注解的主要内容,如果未能解决你的问题,请参考以下文章