Python 3.9 引入了
Annotated
类,它允许添加任意元数据来键入提示,例如,
class A:
x: Annotated[int, "this is x"]
可以通过设置
include_extras
的新 get_type_hints
参数来获取带注释的类型提示:
>>> get_type_hints(A, include_extras=True)
{'x': typing.Annotated[int, 'this is x']}
并且可以通过类型提示的
__metadata__
属性来访问元数据本身。
>>> h = get_type_hints(A, include_extras=True)
>>> h["x"].__metadata__
('this is x',)
但是,我的问题是,测试类型提示是否is
Annotated
的正确方法是什么?也就是说,类似于:
if IS_ANNOTATED(h["x"]):
# do something with the metadata
据我所知,没有记录的方法可以做到这一点,并且有几种可能的方法,但没有一种似乎是理想的。
将
type
与 Annotated
进行比较不起作用,因为类型提示不是 Annotated
的实例:
>>> type(h["x"])
typing._AnnotatedAlias
所以我们必须做:
if type(h["x"]) is _AnnotatedAlias:
...
但是,考虑到
_AnnotatedAlias
中的前导下划线,这可能需要使用实现细节。
另一个选项是直接检查
__metadata__
属性:
if hasattr(h["x"], "__metadata__"):
...
但这假设
__metadata__
属性是 Annotated
所独有的,在处理用户定义的类型提示时也不一定可以假设。
那么,是否有更好的方法来进行此测试?
您可以使用
typing.get_origin
(https://docs.python.org/3/library/typing.html#typing.get_origin)来做到这一点:
assert typing.get_origin(Annotated[int, 0]) is Annotated
assert typing.get_args(Annotated[int, 0]) == (int, 0)
这个怎么样?
from typing import Annotated, Any
annot_type = type(Annotated[int, 'spam'])
def is_annotated(hint: Any, annot_type=annot_type) -> bool:
return (type(hint) is annot_type) and hasattr(hint, '__metadata__')
或者,使用新的 PEP 647:
from typing import Annotated, TypeGuard, Any
annot_type = type(Annotated[int, 'spam'])
def is_annotated(hint: Any, annot_type=annot_type) -> TypeGuard[annot_type]:
return (type(hint) is annot_type) and hasattr(hint, '__metadata__')
此解决方案避免了直接使用任何实现细节。为了安全起见,我在其中添加了额外的
hasattr(hint, '__metadata__')
测试。
有趣的是,这个解决方案似乎与 Python 目前在
inspect
模块中实现多个功能的方式非常相似。 inspect.isfunction
目前的实现如下:
# inspect.py
# -- snip --
import types
# -- snip --
def isfunction(object):
"""Return true if the object is a user-defined function.
Function objects provide these attributes:
__doc__ documentation string
__name__ name with which this function was defined
__code__ code object containing compiled function bytecode
__defaults__ tuple of any default values for arguments
__globals__ global namespace in which this function was defined
__annotations__ dict of parameter annotations
__kwdefaults__ dict of keyword only parameters with defaults"""
return isinstance(object, types.FunctionType)
types
模块查找FunctionType
的定义,你会发现它的定义如下:
# types.py
"""
Define names for built-in types that aren't directly accessible as a builtin.
"""
# -- snip --
def _f(): pass
FunctionType = type(_f)
当然,因为
function
对象的确切性质取决于Python在C级别的实现细节。