什么是检查对象是否是打字的正确方法。通用?

时间:2018-03-08 10:45:15

标签: python generics typing

我试图编写验证类型提示的代码,为了做到这一点,我必须找出注释是什么类型的对象。例如,请考虑这个片段,它应该告诉用户期望的值是什么类型:

class ChapterCreateView(generic.CreateView):
    model = 'Chapter'
    fields = ['name']

    def form_valid(self, form):
        book = get_object_or_404(Book, pk=self.kwargs['book'])
        form.instance.book = book
        return super(ChapterCreateView, self).form_valid(form)

这应该打印"值类型应该是(int,str)"之一,但它会引发异常:

import typing

typ = typing.Union[int, str]

if issubclass(typ, typing.Union):
    print('value type should be one of', typ.__args__)
elif issubclass(typ, typing.Generic):
    print('value type should be a structure of', typ.__args__[0])
else:
    print('value type should be', typ)

Traceback (most recent call last): File "untitled.py", line 6, in <module> if issubclass(typ, typing.Union): File "C:\Python34\lib\site-packages\typing.py", line 829, in __subclasscheck__ raise TypeError("Unions cannot be used with issubclass().") TypeError: Unions cannot be used with issubclass(). 也无效:

isinstance

检查>>> isinstance(typ, typing.Union) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python34\lib\site-packages\typing.py", line 826, in __instancecheck__ raise TypeError("Unions cannot be used with isinstance().") TypeError: Unions cannot be used with isinstance(). 是否为typ的正确方法是什么?

如果可能的话,我希望看到一个由文档或PEP或其他资源支持的解决方案。 A&#34;解决方案&#34; &#34;工作&#34;通过访问未记录的内部属性很容易找到。但更有可能的是,它将成为一个实现细节,并将在未来的版本中发生变化。我正在寻找&#34;正确的方式&#34; 来做这件事。

4 个答案:

答案 0 :(得分:10)

您可能正在寻找__origin__

# * __origin__ keeps a reference to a type that was subscripted,
#   e.g., Union[T, int].__origin__ == Union;`
import typing

typ = typing.Union[int, str]

if typ.__origin__ is typing.Union:
    print('value type should be one of', typ.__args__)
elif typ.__origin__ is typing.Generic:
    print('value type should be a structure of', typ.__args__[0])
else:
    print('value type should be', typ)

>>>value type should be one of (<class 'int'>, <class 'str'>)

我能找到的最好的提倡使用这个无证件的属性是令人放心的quote来自Guido Van Rossum(2年前):

  

我可以推荐的最好的方法是使用__origin__ - 如果我们要更改此属性,仍然需要使用其他方式来访问相同的信息,并且很容易为代码发生问题__origin__。 (我不再担心__origin__的变化而不是__extra__。)您还可以查看内部函数_gorg()_geqv()(这些名称不会显然,它是任何公共API的一部分,但它们的实现非常简单且在概念上很有用。)

文档中的这个警告似乎表明大理石中没有任何东西设置:

  

如果核心开发人员认为有必要,可能会添加新功能,即使在次要版本之间API也可能发生变化。

答案 1 :(得分:3)

没有获得此信息的官方方法。 typing模块仍在大量开发中,并且没有公共API可言。 (实际上,它可能永远不会有一个。)

我们所能做的就是查看模块的内部结构,找到获取我们所需要信息的最简单的方法。并且由于该模块仍在开发中,其内部结构将发生变化。很多。


在python 3.5和3.6中,泛型具有一个__origin__属性,该属性持有对原始泛型基类的引用(即List[int].__origin__将是List),但此属性已更改在3.7中。现在,找出某物是否通用的最简单方法可能是检查其__parameters____args__属性。

这是一组可用于检测泛型的函数:

import typing


if hasattr(typing, '_GenericAlias'):
    # python 3.7
    def _is_generic(cls):
        if isinstance(cls, typing._GenericAlias):
            return True

        if isinstance(cls, typing._SpecialForm):
            return cls not in {typing.Any}

        return False


    def _is_base_generic(cls):
        if isinstance(cls, typing._GenericAlias):
            if cls.__origin__ in {typing.Generic, typing._Protocol}:
                return False

            if isinstance(cls, typing._VariadicGenericAlias):
                return True

            return len(cls.__parameters__) > 0

        if isinstance(cls, typing._SpecialForm):
            return cls._name in {'ClassVar', 'Union', 'Optional'}

        return False
else:
    # python <3.7
    if hasattr(typing, '_Union'):
        # python 3.6
        def _is_generic(cls):
            if isinstance(cls, (typing.GenericMeta, typing._Union, typing._Optional, typing._ClassVar)):
                return True

            return False


        def _is_base_generic(cls):
            if isinstance(cls, (typing.GenericMeta, typing._Union)):
                return cls.__args__ in {None, ()}

            if isinstance(cls, typing._Optional):
                return True

            return False
    else:
        # python 3.5
        def _is_generic(cls):
            if isinstance(cls, (typing.GenericMeta, typing.UnionMeta, typing.OptionalMeta, typing.CallableMeta, typing.TupleMeta)):
                return True

            return False


        def _is_base_generic(cls):
            if isinstance(cls, typing.GenericMeta):
                return all(isinstance(arg, typing.TypeVar) for arg in cls.__parameters__)

            if isinstance(cls, typing.UnionMeta):
                return cls.__union_params__ is None

            if isinstance(cls, typing.TupleMeta):
                return cls.__tuple_params__ is None

            if isinstance(cls, typing.CallableMeta):
                return cls.__args__ is None

            if isinstance(cls, typing.OptionalMeta):
                return True

            return False


def is_generic(cls):
    """
    Detects any kind of generic, for example `List` or `List[int]`. This includes "special" types like
    Union and Tuple - anything that's subscriptable, basically.
    """
    return _is_generic(cls)


def is_base_generic(cls):
    """
    Detects generic base classes, for example `List` (but not `List[int]`)
    """
    return _is_base_generic(cls)


def is_qualified_generic(cls):
    """
    Detects generics with arguments, for example `List[int]` (but not `List`)
    """
    return is_generic(cls) and not is_base_generic(cls)

所有这些功能都应在所有<= 3.7的python版本中起作用(包括使用typing模块backport的所有<3.5)。

答案 2 :(得分:2)

正如 sonny-garcia 在评论中指出的那样,get_origin() 从 python 3.8 开始工作

import typing
from typing import get_origin

typ = typing.Union[int, str]
get_origin(typ) == typing.Union
#True

您可以在docs

中找到更多详细信息

答案 3 :(得分:-2)

我认为,您可以做的最多就是在变量上使用typ,在其上使用typing.get_type_hints并从返回的__annotations__中提取所需的信息 - 就像字典一样

PEP-484说:

  

get_type_hints(),一个实用程序函数,用于从函数或方法中检索类型提示。给定一个函数或方法对象,它返回一个格式与__annotations__相同的dict,但是将前向引用(以字符串文字形式给出)作为原始函数或方法定义的上下文中的表达式进行评估。

26.1.7. Classes, functions, and decorators说:

  

在运行时,isinstance(x, T)会引发TypeError。通常,isinstance()issubclass()不应与类型一起使用。

但是,PEP-526在“非目标”中说:

  

虽然提案附带了typing.get_type_hints标准库函数的扩展,用于注释的运行时检索,但是变量注释不是为运行时类型检查而设计的。必须开发第三方软件包才能实现此类功能。