如何访问 typing.Generic 的类型参数?
- 2025-02-27 09:07:00
- admin 原创
- 70
问题描述:
该typing
模块为泛型类型提示提供了一个基类:typing.Generic
类。
接受方括号中类型参数的子类Generic
,例如:
list_of_ints = typing.List[int]
str_to_bool_dict = typing.Dict[str, bool]
我的问题是,我如何访问这些类型参数?
也就是说,str_to_bool_dict
作为输入,我如何获得str
并bool
作为输出?
基本上我正在寻找这样的功能
>>> magic_function(str_to_bool_dict)
(<class 'str'>, <class 'bool'>)
解决方案 1:
Python >= 3.8
从 Python3.8 开始有typing.get_args
:
print( get_args( List[int] ) ) # (<class 'int'>,)
PEP-560还提供了,它允许我们使用第 n个泛型基数__orig_bases__[n]
的参数:
from typing import TypeVar, Generic, get_args
T = TypeVar( "T" )
class Base( Generic[T] ):
pass
class Derived( Base[int] ):
pass
print( get_args( Derived.__orig_bases__[0] ) ) # (<class 'int'>,)
Python >= 3.6
从 Python 3.6 开始,有一个公共的__args__
and ( __parameters__
) 字段。例如:
print( typing.List[int].__args__ )
它包含泛型参数(即int
),同时__parameters__
包含泛型本身(即~T
)。
Python < 3.6
使用typing_inspect.getargs
一些注意事项
typing
遵循PEP8。 PEP8 和typing
均由 Guido van Rossum 共同撰写。 双前下划线和尾下划线在 中定义为: “位于用户控制的命名空间中的“魔法”对象或属性”。
双下划线部分也进行了内联注释;从官方的打字存储库中我们可以看到:
“
__args__
是下标中使用的所有参数的元组,例如Dict[T, int].__args__ == (T, int)
”。
然而,作者还指出:
“type 模块处于临时状态,因此它不受向后兼容性的高标准保护(尽管我们尝试尽可能地保留它),对于(尚未记录的)dunder 属性尤其如此
__union_params__
。如果您想在运行时上下文中使用 typing 类型,那么您可能会对该typing_inspect
项目感兴趣(其中的一部分可能最终会在以后的 typing 中出现)。”
一般来说,无论你做什么,typing
都需要暂时保持最新状态。如果你需要向前兼容的更改,我建议你编写自己的注释类。
解决方案 2:
据我所知,这里没有令人满意的答案。
我想到的是__args__
存储此信息的未记录的属性:
list_of_ints.__args__
>>>(<class 'int'>,)
str_to_bool_dict.__args__
>>>(<class 'str'>, <class 'bool'>)
但模块的文档中没有提及它typing
。
值得注意的是,它几乎在文档中被提及:
也许我们还应该讨论是否需要记录 的所有关键字参数
GenericMeta.__new__
。有tvars
、args
、origin
、extra
和orig_bases
。我认为我们可以谈谈前三个(它们对应于__parameters__
、__args__
、 和__origin__
,这些在打字中被大多数东西使用)。
但它并没有成功:
我根据问题中的讨论添加了和文档字符串。我决定不在文档字符串中描述
GenericMeta
和朋友__all__
。相反,我只是在它们第一次使用的地方添加了一条注释。GenericMeta
`GenericMeta.__new__`__origin__
从那里开始,你仍然有三个非互斥的选择:
等待
typing
模块完全成熟,并希望这些功能能够很快被记录下来加入Python 创意邮件列表,看看是否可以收集足够的支持以使这些内部功能公开/成为 API 的一部分
同时处理未记录的内部结构,赌这些结构不会发生改变或者改变很小。
请注意,第三点几乎无法避免,因为API 也可能会发生变化:
打字模块已临时包含在标准库中。如果核心开发人员认为有必要,可能会添加新功能,并且 API 甚至可能在次要版本之间发生变化。
解决方案 3:
看来这个内部方法可以解决问题
typing.List[int]._subs_tree()
返回元组:
(typing.List, <class 'int'>)
但这是一个私有 API,可能有更好的答案。
解决方案 4:
问题专门询问了typing.Generic
,但事实证明(至少在typing
模块的早期版本中)并非所有可下标类型都是 的子类Generic
。在较新的版本中,所有可下标类型都将其参数存储在__args__
属性中:
>>> List[int].__args__
(<class 'int'>,)
>>> Tuple[int, str].__args__
(<class 'int'>, <class 'str'>)
然而,在 Python 3.5 中,一些类(如typing.Tuple
)typing.Union
将typing.Callable
它们存储在不同的属性中,如__tuple_params__
,__union_params__
或通常存储在中__parameters__
。为了完整起见,这里有一个函数,它可以从任何 Python 版本中的任何可下标类型中提取类型参数:
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)
示范:
>>> get_subtypes(List[int])
(<class 'int'>,)
>>> get_subtypes(Tuple[str, bool])
(<class 'str'>, <class 'bool'>)
解决方案 5:
在你的构造上使用.__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__
听起来很有帮助。如果没有 self 和 就无法工作,所以我们只剩__args__
下和。__origin__
`__extra____origin__
parameters`__args__
>>> str_to_bool_dict.__args__
(<class 'str'>, <class 'bool'>)
答案由此而来。
自省允许py.test
的assert
语句使 JUnit 派生的测试框架看起来过时。即使是 JavaScript / Elm / Clojure 等语言也没有像 Python 那样直截了当的东西dir
。Python 的命名约定允许您无需实际阅读(在某些情况下需要理解)文档即可发现该语言。
因此,请使用内省法进行搜寻,并阅读文档/邮件列表来确认您的发现。
PS 致 OP -- 此方法也回答了您的问题:检查对象是否为 typing.Generic 的正确方法是什么?如果您无法提交邮件列表或您是忙碌的开发人员,请使用发现 - 这是在 python 中执行此操作的方法。
扫码咨询,免费领取项目管理大礼包!