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

red*_*dow 16 python type-annotation python-3.7

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

在Python 3.5/3.6上,它可以轻松实现,正如您所期望的那样:

>>> from typing import List

>>> isinstance(List[str], type)
True

>>> issubclass(List[str], List)
True
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

想到的其他想法是检查实际的实例类型,但是:

Python 3.6/3.5:

>>> type(List[str])
<class 'typing.GenericMeta'>
Run Code Online (Sandbox Code Playgroud)

Python 3.7:

>>> type(List[str])
<class 'typing._GenericAlias'>
Run Code Online (Sandbox Code Playgroud)

但这并没有真正给出任何进一步的指示,即哪个是实际的泛型类型(可能不是List); 此外,以这种方式进行检查感觉非常错误,特别是因为_GenericAlias现在变成了"私人"类型(注意下划线).

可以检查的另一件事是__origin__关于类型的论证,但这也不是正确的方法.

它在3.7上仍有所不同:

>>> List[str].__origin__
<class 'list'>
Run Code Online (Sandbox Code Playgroud)

而3.5/3.6:

>>> List[str].__origin__
typing.List
Run Code Online (Sandbox Code Playgroud)

我一直在寻找这种"正确"的方式,但还没有在Python docs/google搜索中找到它.

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

更新:关于用例

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

因此,我的用例是在函数签名(参数类型/默认值,返回类型,docstring)上使用内省来自动为它们生成GraphQL架构(从而减少样板量).

我仍然有点不知道这是否是一个好主意.

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

我想知道从typing这种方式支持使用类型的泛型类型(列表,dicts,联合......)是否会增加太多"黑魔法",这可能会以意想不到的方式破坏.(现在这不是一个大问题,但是未来的Python版本会超过3.7?这会成为维护的噩梦吗?).

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

..但在不利方面,这将迫使人们记住他们必须使用自定义类型注释.此外,我不确定mypy将如何解决这个问题(我假设需要在某个地方声明自定义类型与typing.List... 完全兼容?仍然听起来像是hackish).

(我主要是要求对两种方法的建议,以及最重要的优点任何/两个选择我可能会错过的利弊.希望这不会成为"过宽"那么..).

Max*_*ner 7

Python 3.8 添加typing.get_origin()typing.get_args()支持基本的自省。

这些 API 也已在https://pypi.org/project/typing-compat/ 中向后移植到 Python >=3.5 。

请注意,typing.get_args在 3.7 中调用裸泛型时的行为仍然略有不同;在 3.8 中typing.get_args(typing.Dict)(),但在 3.7 中是(~KT, ~VT)(和其他泛型类似),其中~KT~VT是类型的对象typing.TypeVar


Mar*_*ers 5

首先:没有定义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__
Run Code Online (Sandbox Code Playgroud)

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

  • 我实在说不上来。你得直接问开发商。也许您可以尝试在 Python-Dev 邮件列表上发帖。也就是说,我非常怀疑运行时内省是否会消失,这违背了流行的 Python 文化。 (3认同)