如何访问typing.Generic的类型参数?

时间:2018-02-01 22:31:35

标签: python generics

typing模块为泛型类型提示提供了一个基类:typing.Generic类。

方括号中的Generic接受类型参数的子类,例如:

list_of_ints = typing.List[int]
str_to_bool_dict = typing.Dict[str, bool]

我的问题是,如何访问这些类型的参数?

也就是说,如果输入str_to_bool_dict,我如何才能将strbool作为输出?

基本上我正在寻找一个这样的功能

>>> magic_function(str_to_bool_dict)
(<class 'str'>, <class 'bool'>)

5 个答案:

答案 0 :(得分:16)

可能性1

从Python 3.6开始。有一个公开__args__和(__parameters__)字段。 例如:

print( typing.List[int].__args__ )

这包含通用参数(即int),而__parameters__包含通用本身(即~T)。

可能性2

使用typing_inspect.getargs

使用哪种

typing跟随PEP8。 PEP8和typing都是由Guido van Rossum合着的。双引导和尾随下划线定义为:&#34;“魔术”对象或属于用户控制的命名空间&#34; 的属性。

dunders也在线评论;来自typing的官方存储库我们 可以看到: * &#34; __args__是下标中使用的所有参数的元组,例如Dict[T, int].__args__ == (T, int)&#34;。

然而,authors also note: * &#34;打字模块具有临时状态,因此不符合高标准的向后兼容性(尽管我们尽可能地保持它),对于(尚未记录的)dunder尤其如此像__union_params__这样的属性。如果你想在运行时上下文中使用输入类型,那么你可能会对typing_inspect项目感兴趣(其中一部分可能会在以后输入时结束)。&#34;

我很一般,无论你对typing做什么,都需要暂时保持最新状态。如果您需要向前兼容的更改,我建议您编写自己的注释类。

答案 1 :(得分:5)

似乎这种内部方法可以解决问题

typing.List[int]._subs_tree()

返回元组:

(typing.List, <class 'int'>)

但这是一个私有API,可能有更好的答案。

答案 2 :(得分:4)

据我所知,这里没有幸福的答案。

我想到的是__args__未记录的属性,它存储了这些信息:

list_of_ints.__args__
>>>(<class 'int'>,)

str_to_bool_dict.__args__
>>>(<class 'str'>, <class 'bool'>)

但在typing模块的文档中没有提及它。

值得注意的是,文档中只有very close to be mentioned

  

可能我们还应讨论是否需要记录GenericMeta.__new__的所有关键字参数。有tvarsargsoriginextraorig_bases。我想我们可以说一下前三个(它们对应__parameters____args____origin__,大多数情况下都会使用这些内容。

但是it did not quite make it

  

我将GenericMeta添加到__all__,并在问题讨论后将文档字符串添加到GenericMetaGenericMeta.__new__。   我决定不在文档字符串中描述__origin__和朋友。相反,我只是在他们第一次使用的地方添加了评论。

从那里,您仍然有三个非互斥选项:

  • 等待typing模块完全成熟,并希望尽快记录这些功能

  • 加入Python ideas mailing list并查看是否可以收集足够的支持以使这些内部公开/部分API

  • 与此同时与无证件的内部人员合作,进行赌博,不会对这些内容进行更改或更改将是次要的。

请注意,即使是API can be subject to changes

,也很难避免第三点
  

打字模块已临时包含在标准库中。如果核心开发人员认为有必要,可能会添加新功能并且 API可能会在次要版本之间发生变化

答案 3 :(得分:0)

在构造上使用.__args__。所以你需要的神奇功能就像 -

get_type_args = lambda genrc_type: getattr(genrc_type, '__args__')
  

我的问题是,如何访问这些类型的参数?

在这种情况下 - 我如何访问......

使用Python强大的内省功能。

即使作为非专业程序员,我知道我正在尝试检查内容,dir是一个类似于终端中的IDE的功能。所以

之后
>>> import typing
>>> str_to_bool_dict = typing.Dict[str, bool]

我想知道是否有任何你想要的魔法

>>> methods = dir(str_to_bool_dict)
>>> methods
['__abstractmethods__', '__args__', .....]

我看到太多信息,看我是否正确我确认

>>> len(methods)
53
>>> len(dir(dict))
39

现在让我们找到专门为通用类型设计的方法

>>> set(methods).difference(set(dir(dict)))
{'__slots__', '__parameters__', '_abc_negative_cache_version', '__extra__',
'_abc_cache', '__args__', '_abc_negative_cache', '__origin__',
'__abstractmethods__', '__module__', '__next_in_mro__', '_abc_registry',
'__dict__', '__weakref__'}

其中,__parameters____extra____args____origin__听起来很有帮助。如果没有自己,__extra____origin__将无效,因此我们会留下__parameters____args__

>>> str_to_bool_dict.__args__
(<class 'str'>, <class 'bool'>)

因此答案。

Introspection允许py.test的{​​{1}}语句使JUnit派生的测试框架看起来过时。甚至像JavaScript / Elm / Clojure这样的语言也没有像Python assert这样的直截了当的东西。 Python的命名约定允许您发现语言,而无需实际阅读(在某些情况下,例如这些)文档。

使用内省和阅读文档/邮件列表来确认您的发现。

P.S。对OP - 如果你不能提交邮件列表或者是一个繁忙的开发人员,这个方法也会回答你的问题What's the correct way to check if an object is a typing.Generic?使用发现 - 这是在python中实现它的方法。

答案 4 :(得分:0)

该问题专门询问了typing.Generic,但事实证明(至少在typing模块的较早版本中),并非所有可下标类型都是Generic的子类。在较新的版本中,所有可下标类型将其参数存储在__args__属性中:

>>> List[int].__args__
(<class 'int'>,)
>>> Tuple[int, str].__args__
(<class 'int'>, <class 'str'>)

但是,在python 3.5中,某些类,例如typing.Tupletyping.Uniontyping.Callable将它们存储在不同的属性中,例如__tuple_params____union_params__或通常存储在{ {1}}。为了完整起见,下面的函数可以从任何python版本的任何可下标类型中提取类型参数:

__parameters__

演示:

import typing


if hasattr(typing, '_GenericAlias'):
    # python 3.7
    def _get_base_generic(cls):
        # subclasses of Generic will have their _name set to None, but
        # their __origin__ will point to the base generic
        if cls._name is None:
            return cls.__origin__
        else:
            return getattr(typing, cls._name)
else:
    # python <3.7
    def _get_base_generic(cls):
        try:
            return cls.__origin__
        except AttributeError:
            pass

        name = type(cls).__name__
        if not name.endswith('Meta'):
            raise NotImplementedError("Cannot determine base of {}".format(cls))

        name = name[:-4]
        try:
            return getattr(typing, name)
        except AttributeError:
            raise NotImplementedError("Cannot determine base of {}".format(cls))


if hasattr(typing.List, '__args__'):
    # python 3.6+
    def _get_subtypes(cls):
        subtypes = cls.__args__

        if _get_base_generic(cls) is typing.Callable:
            if len(subtypes) != 2 or subtypes[0] is not ...:
                subtypes = (subtypes[:-1], subtypes[-1])

        return subtypes
else:
    # python 3.5
    def _get_subtypes(cls):
        if isinstance(cls, typing.CallableMeta):
            if cls.__args__ is None:
                return ()

            return cls.__args__, cls.__result__

        for name in ['__parameters__', '__union_params__', '__tuple_params__']:
            try:
                subtypes = getattr(cls, name)
                break
            except AttributeError:
                pass
        else:
            raise NotImplementedError("Cannot extract subtypes from {}".format(cls))

        subtypes = [typ for typ in subtypes if not isinstance(typ, typing.TypeVar)]
        return subtypes


def get_subtypes(cls):
    """
    Given a qualified generic (like List[int] or Tuple[str, bool]) as input, return
    a tuple of all the classes listed inside the square brackets.
    """
    return _get_subtypes(cls)