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'>)
答案 0 :(得分:16)
从Python 3.6开始。有一个公开__args__
和(__parameters__
)字段。
例如:
print( typing.List[int].__args__ )
这包含通用参数(即int
),而__parameters__
包含通用本身(即~T
)。
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__
的所有关键字参数。有tvars
,args
,origin
,extra
和orig_bases
。我想我们可以说一下前三个(它们对应__parameters__
,__args__
和__origin__
,大多数情况下都会使用这些内容。
我将
GenericMeta
添加到__all__
,并在问题讨论后将文档字符串添加到GenericMeta
和GenericMeta.__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.Tuple
,typing.Union
和typing.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)