检查对象是否为类型的正确方法是什么?泛型? [英] What's the correct way to check if an object is a typing.Generic?

查看:155
本文介绍了检查对象是否为类型的正确方法是什么?泛型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写验证类型提示的代码,为了做到这一点,我必须找出注释是哪种对象.例如,考虑以下片段,该片段应告诉用户期望什么样的价值:

I'm trying to write code that validates type hints, and in order to do so I have to find out what kind of object the annotation is. For example, consider this snippet that's supposed to tell the user what kind of value is expected:

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)

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

This should print "value type should be one of (int, str)", but instead it throws an exception:

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是否为typing.Generic的正确方法是什么?


What's the correct way to check if typ is a typing.Generic?

如果可能,我希望看到一个由文档或PEP或其他资源支持的解决方案.通过访问未记录的内部属性起作用"的解决方案"很容易找到.但是很有可能,它将成为实现细节,并且在将来的版本中会有所变化.我正在寻找正确的方式" 来做到这一点.

If possible, I would like to see a solution that's backed by documentation or a PEP or some other resource. A "solution" that "works" by accessing undocumented, internal attributes is easy to find. But more likely than not, it'll turn out to be an implementation detail and will change in future versions. I'm looking for "the right way" to do it.

推荐答案

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

There is no official way to obtain this information. The typing module is still in heavy development, and has no public API to speak of. (In fact, it will probably never have one.)

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

All we can do is to look at the module's internals and find the least gross way to get the information we're after. And because the module is still being worked on, its internals will change. A lot.

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

In python 3.5 and 3.6, generics had an __origin__ attribute that held a reference to the original generic base class (i.e. List[int].__origin__ would've been List), but this was changed in 3.7. Now the easiest way to find out if something is a generic is probably to check its __parameters__ and __args__ attributes.

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

Here is a set of functions that can be used to detect generics:

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模块反向移植的所有< 3.5内容).

All of these functions should work in all python versions <= 3.7 (including anything <3.5 that uses the typing module backport).

这篇关于检查对象是否为类型的正确方法是什么?泛型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆