Python 3.7:检查类型注释是否是泛型的“子类”

问题描述 投票:0回答:3

我正在尝试找到一种可靠/跨版本(3.5+)的方法来检查类型注释是否是给定泛型类型的“子类”(即从类型注释对象中获取泛型类型)。

在 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
现在变成了“私有”类型(注意下划线)。

人们可以检查的另一件事是类型上的

__origin__
参数,但这感觉也不是正确的方法。

3.7 上仍然有所不同:

>>> List[str].__origin__
<class 'list'>

3.5 / 3.6 时:

>>> List[str].__origin__
typing.List

我一直在寻找“正确”的方法来做到这一点,但在Python文档/谷歌搜索中没有找到它。

现在,我假设必须有一种干净的方法来进行此检查,因为像 mypy 这样的工具将依赖它来进行类型检查..?

更新:关于用例

好的,在这里添加更多上下文..

因此,我的用例是对函数签名(参数类型/默认值、返回类型、文档字符串)使用内省来自动为它们生成 GraphQL 模式(从而减少样板文件的数量)。

对于这是否是一个好主意,我仍然有点犹豫。

从可用性的角度来看,我喜欢它(无需学习另一种声明函数签名的方法:只需以通常的方式注释您的类型);请参阅此处的两个代码示例以了解我的意思:https://github.com/rshk/pyql

我想知道使用

typing
中的类型支持泛型类型(列表、字典、联合等)是否会增加太多“黑魔法”,可能会以意想不到的方式破坏。 (目前这不是一个大问题,但是未来的 Python 版本,3.7 之后呢?这会成为维护噩梦吗?)。

当然,替代方案是仅使用支持更可靠/面向未来的检查的自定义类型注释,例如:https://github.com/rshk/pyql/blob/master/pyql/schema/types/ core.py#L337-L339

..但缺点是,这会迫使人们记住他们必须使用自定义类型注释。此外,我不确定 mypy 将如何处理这个问题(我认为需要在某处声明自定义类型与

typing.List
完全兼容..?听起来还是很黑客)。

(我主要是寻求有关这两种方法的建议,最重要的是我可能错过的两种替代方案的优点/缺点。希望这不会变得“太广泛”..)。

python python-3.7 type-annotation
3个回答
15
投票

首先:没有定义 API 来按照

typing
模块的定义来内省类型提示对象。类型提示工具应该处理源代码,即文本,而不是运行时的 Python 对象;
mypy
不会内省
List[str]
对象,而是处理源代码的已解析抽象语法树

因此,虽然您始终可以访问

__origin__
之类的属性,但您本质上是在处理实现细节(内部簿记),并且这些实现细节可以并且将会随着版本的不同而改变。

也就是说,核心 mypy/打字贡献者创建了

typing_inspect
模块 来开发类型提示的内省 API。该项目仍然将自己记录为“实验性”,并且您可以预期它也会随着时间而改变,直到它不再是实验性的。它不会在这里解决您的问题,因为它不支持 Python 3.5,并且它的 get_origin() 函数返回与
__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 版本中崩溃。

    


14
投票
typing.get_origin()

typing.get_args()
来支持基本的内省。
这些 API 也已向后移植到 Python >=3.5 

https://pypi.org/project/typing-compat/

请注意,在 3.7 中调用裸泛型时

typing.get_args

的行为仍然略有不同;在 3.8 中

typing.get_args(typing.Dict)
()
,但在 3.7 中它是
(~KT, ~VT)
(对于其他泛型也类似),其中
~KT
~VT
typing.TypeVar
类型的对象。
    


0
投票
然后

>>> 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+。
    

© www.soinside.com 2019 - 2024. All rights reserved.