Python 3.7: 检查类型注解是否为 "subclass" 泛型
Python 3.7: check if type annotation is "subclass" of generic
我正在尝试找到一种可靠的/跨版本 (3.5+) 方法来检查类型注释是否是给定泛型类型的 "subclass"(即从类型中获取泛型类型注释对象)。
在 Python 3.5 / 3.6 上,如您所料,它运行起来轻而易举:
>>> from typing import List
>>> isinstance(List[str], type)
True
>>> issubclass(List[str], List)
True
而在 3.7 上,泛型实例似乎不再是 type
的实例,因此它会失败:
>>> from typing import List
>>> isinstance(List[str], type)
False
>>> issubclass(List[str], List)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.7/typing.py", line 716, in __subclasscheck__
raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks
想到的其他想法是检查实际实例类型,但是:
Python 3.6 / 3.5:
>>> type(List[str])
<class 'typing.GenericMeta'>
Python 3.7:
>>> type(List[str])
<class 'typing._GenericAlias'>
但这并没有真正进一步说明哪个是实际的通用类型(可能不是 List);此外,以这种方式进行检查感觉很不对,特别是因为 _GenericAlias
现在变成了 "private" 类型(注意下划线)。
可以检查的另一件事是关于类型的 __origin__
参数,但这也不是正确的方法。
它在 3.7 上仍然不同:
>>> List[str].__origin__
<class 'list'>
而 3.5 / 3.6:
>>> List[str].__origin__
typing.List
我一直在寻找 "right" 的方法,但在 Python 文档/google 搜索中没有找到。
现在,我假设必须有一种干净的方法来进行这种检查,因为像 mypy 这样的工具会依赖它来进行类型检查..?
更新:关于用例
好的,在这里添加更多上下文..
因此,我的用例是对函数签名(参数类型/默认值、return 类型、文档字符串)进行自省,以自动为它们生成 GraphQL 模式(从而减少样板文件的数量)。
关于这是否是个好主意,我仍然有点纠结。
从可用性的角度来看,我喜欢它(无需学习另一种声明函数签名的方法:只需以通常的方式注释您的类型);请参阅此处的两个代码示例以了解我的意思:https://github.com/rshk/pyql
我想知道以这种方式使用 typing
中的类型来支持泛型类型(列表、字典、联合...)是否会增加太多 "black magic",这可能会以意想不到的方式中断。 (目前这不是一个大问题,但是未来的 Python 版本,超过 3.7 会怎么样?这会成为维护的噩梦吗?)。
当然,另一种方法是只使用支持更可靠/面向未来的检查的自定义类型注释,例如:https://github.com/rshk/pyql/blob/master/pyql/schema/types/core.py#L337-L339
..但不利的一面是,这会迫使人们记住他们必须使用自定义类型注释。此外,我不确定 mypy 将如何处理(我假设需要在某处声明自定义类型与 typing.List
完全兼容。..?听起来仍然很老套)。
(我主要是征求关于这两种方法的建议,最重要的是我可能错过的两种选择中的任何 pros/cons。希望这不会变成 "too broad" SO。 .).
首先:没有定义 API 来内省由 typing
模块定义的类型提示对象。类型提示工具应该在运行时处理 源代码 所以文本,而不是 Python 对象; mypy
不会内省 List[str]
对象,而是处理经过解析的 Abstract Syntax Tree 您的源代码。
因此,虽然您始终可以访问 __origin__
之类的属性,但您实际上是在处理实现细节 (internal bookkeeping),并且这些实现细节可以并且将会随着版本的不同而变化。
也就是说,核心 mypy / 打字贡献者创建了 typing_inspect
module 来开发类型提示的内省 API。该项目仍然将自己记录为 实验性 ,并且您可以预期它也会随着时间的推移而改变,直到它不再是实验性的。它不会在这里解决您的问题,因为它不支持 Python 3.5,并且它的 get_origin()
函数 returns 与 __origin__
属性提供的值完全相同。
排除所有这些警告,您想要在 Python 3.5 / Python 3.6 上访问的是 __extra__
属性;这是用于驱动库最初实现的 issubclass()
/ isinstance()
支持的基本内置类型(但自从在 3.7 中删除):
def get_type_class(typ):
try:
# Python 3.5 / 3.6
return typ.__extra__
except AttributeError:
# Python 3.7
return typ.__origin__
无论如何,这会在 Python 3.5 及更高版本中生成 <class 'list'>
。它仍然使用内部实现细节,并且可能会在未来的 Python 版本中中断。
Python 3.8 添加了 typing.get_origin()
和 typing.get_args()
以支持基本内省。
这些 API 也被反向移植到 Python >=3.5 in https://pypi.org/project/typing-compat/。
请注意,在 3.7 中调用裸泛型时,typing.get_args
的行为仍然略有不同;在 3.8 中 typing.get_args(typing.Dict)
是 ()
,但在 3.7 中它是 (~KT, ~VT)
(和其他泛型类似),其中 ~KT
和 ~VT
是 ~VT
类型的对象=18=].
pip install typing_utils
然后
>>> typing_utils.issubtype(typing.List[int], list)
True
>>> typing_utils.issubtype(typing.List, typing.List[int])
False
typing_utils
还向后移植 typing.get_origin
和 typing.get_args
从 Python 3.8 到 3.6+。
我正在尝试找到一种可靠的/跨版本 (3.5+) 方法来检查类型注释是否是给定泛型类型的 "subclass"(即从类型中获取泛型类型注释对象)。
在 Python 3.5 / 3.6 上,如您所料,它运行起来轻而易举:
>>> from typing import List
>>> isinstance(List[str], type)
True
>>> issubclass(List[str], List)
True
而在 3.7 上,泛型实例似乎不再是 type
的实例,因此它会失败:
>>> from typing import List
>>> isinstance(List[str], type)
False
>>> issubclass(List[str], List)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.7/typing.py", line 716, in __subclasscheck__
raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks
想到的其他想法是检查实际实例类型,但是:
Python 3.6 / 3.5:
>>> type(List[str])
<class 'typing.GenericMeta'>
Python 3.7:
>>> type(List[str])
<class 'typing._GenericAlias'>
但这并没有真正进一步说明哪个是实际的通用类型(可能不是 List);此外,以这种方式进行检查感觉很不对,特别是因为 _GenericAlias
现在变成了 "private" 类型(注意下划线)。
可以检查的另一件事是关于类型的 __origin__
参数,但这也不是正确的方法。
它在 3.7 上仍然不同:
>>> List[str].__origin__
<class 'list'>
而 3.5 / 3.6:
>>> List[str].__origin__
typing.List
我一直在寻找 "right" 的方法,但在 Python 文档/google 搜索中没有找到。
现在,我假设必须有一种干净的方法来进行这种检查,因为像 mypy 这样的工具会依赖它来进行类型检查..?
更新:关于用例
好的,在这里添加更多上下文..
因此,我的用例是对函数签名(参数类型/默认值、return 类型、文档字符串)进行自省,以自动为它们生成 GraphQL 模式(从而减少样板文件的数量)。
关于这是否是个好主意,我仍然有点纠结。
从可用性的角度来看,我喜欢它(无需学习另一种声明函数签名的方法:只需以通常的方式注释您的类型);请参阅此处的两个代码示例以了解我的意思:https://github.com/rshk/pyql
我想知道以这种方式使用 typing
中的类型来支持泛型类型(列表、字典、联合...)是否会增加太多 "black magic",这可能会以意想不到的方式中断。 (目前这不是一个大问题,但是未来的 Python 版本,超过 3.7 会怎么样?这会成为维护的噩梦吗?)。
当然,另一种方法是只使用支持更可靠/面向未来的检查的自定义类型注释,例如:https://github.com/rshk/pyql/blob/master/pyql/schema/types/core.py#L337-L339
..但不利的一面是,这会迫使人们记住他们必须使用自定义类型注释。此外,我不确定 mypy 将如何处理(我假设需要在某处声明自定义类型与 typing.List
完全兼容。..?听起来仍然很老套)。
(我主要是征求关于这两种方法的建议,最重要的是我可能错过的两种选择中的任何 pros/cons。希望这不会变成 "too broad" SO。 .).
首先:没有定义 API 来内省由 typing
模块定义的类型提示对象。类型提示工具应该在运行时处理 源代码 所以文本,而不是 Python 对象; mypy
不会内省 List[str]
对象,而是处理经过解析的 Abstract Syntax Tree 您的源代码。
因此,虽然您始终可以访问 __origin__
之类的属性,但您实际上是在处理实现细节 (internal bookkeeping),并且这些实现细节可以并且将会随着版本的不同而变化。
也就是说,核心 mypy / 打字贡献者创建了 typing_inspect
module 来开发类型提示的内省 API。该项目仍然将自己记录为 实验性 ,并且您可以预期它也会随着时间的推移而改变,直到它不再是实验性的。它不会在这里解决您的问题,因为它不支持 Python 3.5,并且它的 get_origin()
函数 returns 与 __origin__
属性提供的值完全相同。
排除所有这些警告,您想要在 Python 3.5 / Python 3.6 上访问的是 __extra__
属性;这是用于驱动库最初实现的 issubclass()
/ isinstance()
支持的基本内置类型(但自从在 3.7 中删除):
def get_type_class(typ):
try:
# Python 3.5 / 3.6
return typ.__extra__
except AttributeError:
# Python 3.7
return typ.__origin__
无论如何,这会在 Python 3.5 及更高版本中生成 <class 'list'>
。它仍然使用内部实现细节,并且可能会在未来的 Python 版本中中断。
Python 3.8 添加了 typing.get_origin()
和 typing.get_args()
以支持基本内省。
这些 API 也被反向移植到 Python >=3.5 in https://pypi.org/project/typing-compat/。
请注意,在 3.7 中调用裸泛型时,typing.get_args
的行为仍然略有不同;在 3.8 中 typing.get_args(typing.Dict)
是 ()
,但在 3.7 中它是 (~KT, ~VT)
(和其他泛型类似),其中 ~KT
和 ~VT
是 ~VT
类型的对象=18=].
pip install typing_utils
然后
>>> typing_utils.issubtype(typing.List[int], list)
True
>>> typing_utils.issubtype(typing.List, typing.List[int])
False
typing_utils
还向后移植 typing.get_origin
和 typing.get_args
从 Python 3.8 到 3.6+。