Python 3.7:检查类型注释是否为通用的“子类”

时间:2018-12-19 15:33:10

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

可以检查的另一件事是类型上的__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中的类型来支持泛型类型(列表,字典,联合等等),会增加过多的“黑魔法”,而这可能会以意想不到的方式破坏。 (目前,这不是一个大问题,但是对于3.7以上的未来Python版本呢?这将成为维护的噩梦吗?)

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

..但不利的一面是,这将迫使人们记住他们必须使用自定义类型注释。而且,我不确定mypy会如何处理(我假设需要在某处声明一个声明,以使自定义类型与typing.List完全兼容。仍然听起来有些黑)。

(我主要是在寻求关于这两种方法的建议,最重要的是,我可能会错过的这两种选择的优点/缺点。希望对于SO来说不会太宽泛。)。 >

3 个答案:

答案 0 :(得分:2)

首先:没有定义typing模块定义的用于内省类型提示对象的API。类型提示工具应该处理源代码,因此文本,而不是运行时的Python对象; mypy不会自省List[str]对象,而是处理经过解析的Abstract Syntax Tree源代码。

因此,尽管您始终可以访问诸如__origin__之类的属性,但实际上您正在处理实现细节(internal bookkeeping),并且这些实现细节可以并且将因版本而异。

也就是说,一个核心的mypy /输入贡献者创建了typing_inspect module来开发用于类型提示的自省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版本中中断。

答案 1 :(得分:1)

请注意,Python 3.8添加了typing.get_origin()typing.get_args()以支持基本的自省。

答案 2 :(得分:0)

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_origintyping.get_args从Python 3.8反向移植到3.6 +。