代码之家  ›  专栏  ›  技术社区  ›  redShadow

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

  •  9
  • redShadow  · 技术社区  · 7 年前

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

    在Python3.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'>
    

    但这并不能进一步说明哪种类型是真正的泛型类型(可能不是列表);此外,用这种方式进行检查感觉非常错误,特别是由于 _GenericAlias 现在变成了“私有”类型(注意下划线)。

    另一个可以检查的是 __origin__ 类型上的参数,但这也不是正确的方式。

    但在3.7版中仍然有所不同:

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

    而3.5/3.6:

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

    我一直在寻找这样做的“正确”方法,但在python docs/google搜索中没有找到。

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

    更新:关于用例

    好的,在这里添加一点上下文。

    因此,我的用例是对函数签名(参数类型/默认值、返回类型、docstring)进行自省,以自动为它们生成一个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 …?听起来还是很粗俗)。

    (我主要是在征求关于这两种方法的建议,最重要的是,我可能忽略了这两种方法的任何利弊。希望这不会变得“太宽泛”了。

    1 回复  |  直到 7 年前
        1
  •  1
  •   Martijn Pieters    7 年前

    首先:没有定义API来内省由 typing 模块。类型提示工具需要处理 源代码 ,所以文本,而不是运行时使用Python对象; mypy 不反省 List[str] 对象,而是处理已解析的 Abstract Syntax Tree 源代码的。

    因此,尽管您可以访问 __origin__ ,您基本上是在处理实现细节( internal bookkeeping )并且这些实现细节可以并且将在版本之间进行更改。

    也就是说,一个核心的mypy/打字贡献者创建了 typing_inspect module 为类型提示开发一个自省API。该项目仍将自身记录为 实验的 你也可以期待它随着时间的推移而改变,直到它不再是实验性的。它在这里不能解决您的问题,因为它不支持Python3.5,而且 get_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版本中有所突破。

    推荐文章