我正在尝试编写验证类型提示的代码,为了做到这一点,我必须找出注释是什么类型的对象。例如,考虑一下该片段应该告诉用户期望的值是什么类型:
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)之一”,而是抛出异常:
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
的正确方法是什么?
如果可能的话,我希望看到一个由文档或PEP或其他资源支持的解决方案。通过访问未记录的内部属性“工作”的“解决方案”很容易找到。但更有可能的是,它将成为一个实现细节,并将在未来的版本中发生变化。我正在寻找“正确的方法”来做到这一点。
您可能正在寻找__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'>)
我能找到的最好的提倡使用这个无证件的属性是令人放心的来自Guido Van Rossum(2年前)的quote:
我可以推荐的最好的是使用
__origin__
- 如果我们要改变这个属性,仍然需要一些其他方法来访问相同的信息,并且很容易为你的代码发出__origin__
。 (我不太担心__origin__
的变化而不是__extra__
的变化。)你也可以看一下内部函数_gorg()
和_geqv()
(这些名称显然不是任何公共API的一部分,但它们的实现非常简单,在概念上很有用)。
文档中的这个警告似乎表明大理石中没有任何设置:
如果核心开发人员认为有必要,可能会添加新功能,甚至可能会在次要版本之间更改API。
没有正式的方法来获取此信息。 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)
所有这些函数都应该适用于所有python版本<= 3.7(包括使用typing
模块backport的任何<3.5)。
我认为,你能做的最多就是在一个变量上使用你的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
标准库函数的扩展,用于注释的运行时检索,但是变量注释不是为运行时类型检查而设计的。必须开发第三方软件包以实现此类功能。