python 3.7 : check if type annotation is "subclass" of generic

标签 python python-3.7 type-annotation

我试图找到一种可靠的/跨版本 (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 现在变成了“私有(private)”类型(请注意下划线)。

另一件可以检查的事情是类型的 __origin__ 参数,但这也不是正确的方法。

它在 3.7 上仍然不同:

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

3.5/3.6 时:

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

我一直在寻找执行此操作的“正确”方法,但尚未在 Python 文档/谷歌搜索中找到它。

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

更新:关于用例

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

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

对于这是否是个好主意,我仍然有些纠结。

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

我想知道以这种方式使用 typing 中的类型来支持泛型类型(列表、字典、联合...)是否会增加太多的“黑魔法”,这可能会以意想不到的方式中断。 (目前这不是一个大问题,但是 future 的 Python 版本,超过 3.7 会怎么样?这会成为维护的噩梦吗?)。

当然,另一种方法是只使用支持更可靠/面向 future 的检查的自定义类型注释,例如:https://github.com/rshk/pyql/blob/master/pyql/schema/types/core.py#L337-L339

..但不利的一面是,这会迫使人们记住他们必须使用自定义类型注释。此外,我不确定 mypy 将如何处理(我假设需要在某处声明自定义类型与 typing.List.. 完全兼容?听起来还是很老套)。

(我主要是征求关于这两种方法的建议,最重要的是,我可能错过了这两种选择的任何优缺点。希望这不会变得“太宽泛”,所以……)。

最佳答案

首先:没有定义 API 来内省(introspection)由 typing 定义的类型提示对象模块。类型提示工具应该在运行时处理源代码,即文本,而不是 Python 对象; mypy不反省List[str]对象,而是处理已解析的 Abstract Syntax Tree您的源代码。

因此,尽管您始终可以访问 __origin__ 等属性,您实际上是在处理实现细节 ( internal bookkeeping ),并且这些实现细节可以并且将会随着版本的不同而变化。

也就是说,核心 mypy/typing 贡献者创建了 typing_inspect module为类型提示开发内省(introspection) 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__

这会产生 <class 'list'>在 Python 3.5 及更高版本中,无论如何。它仍然使用内部实现细节,并且很可能会在未来的 Python 版本中中断。

关于 python 3.7 : check if type annotation is "subclass" of generic,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53854463/

相关文章:

ssl - Python ssl 客户端无法使用 ssl 连接到 socketserver

python - 如何让 mypy 识别或忽略动态导入的类型?

java-8 - 如果是 varargs 参数,@Nullable 注释引用哪里?

python-3.x - 类型注释: Extract inner type from container type

python - seaborn:默认情况下绝望图

python - 立即从同步代码执行异步回调

python: from __future__ import print_function 必须在每个加载的模块中?

python - Pandas 计算 str 系列中的频率

python - Pandas :按行号和列号从数据框中访问数据

python - 为什么 PyQt connect() 语法如此冗长?