检查类型提示是否被注释的正确方法是什么?
What is the right way to check if a type hint is annotated?
Python 3.9 引入了 Annotated
class,它允许添加任意元数据来键入提示,例如,
class A:
x: Annotated[int, "this is x"]
注释类型提示可以通过设置get_type_hints
的新include_extras
参数获得:
>>> 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',)
但是,我的问题是,测试类型提示是否 是 Annotated
的正确方法是什么?也就是说,类似于:
if IS_ANNOTATED(h["x"]):
# do something with the metadata
据我所知,没有记录在案的方法可以做到这一点,但有几种可能的方法,none 其中看起来很理想。
无法将 type
与 Annotated
进行比较,因为类型提示不是 Annotated
的实例:
>>> type(h["x"])
typing._AnnotatedAlias
所以我们要做:
if type(h["x"]) is _AnnotatedAlias:
...
但是,考虑到 _AnnotatedAlias
中的前导下划线,这可能需要使用实现细节。
另一种选择是直接检查 __metadata__
属性:
if hasattr(h["x"], "__metadata__"):
...
但这假定 __metadata__
属性对于 Annotated
是唯一的,在处理用户定义的类型提示时也不一定要假定。
那么,是否有更好的方法来进行此测试?
这个怎么样?
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 级别的实现细节。
Python 3.9 引入了 Annotated
class,它允许添加任意元数据来键入提示,例如,
class A:
x: Annotated[int, "this is x"]
注释类型提示可以通过设置get_type_hints
的新include_extras
参数获得:
>>> 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',)
但是,我的问题是,测试类型提示是否 是 Annotated
的正确方法是什么?也就是说,类似于:
if IS_ANNOTATED(h["x"]):
# do something with the metadata
据我所知,没有记录在案的方法可以做到这一点,但有几种可能的方法,none 其中看起来很理想。
无法将 type
与 Annotated
进行比较,因为类型提示不是 Annotated
的实例:
>>> type(h["x"])
typing._AnnotatedAlias
所以我们要做:
if type(h["x"]) is _AnnotatedAlias:
...
但是,考虑到 _AnnotatedAlias
中的前导下划线,这可能需要使用实现细节。
另一种选择是直接检查 __metadata__
属性:
if hasattr(h["x"], "__metadata__"):
...
但这假定 __metadata__
属性对于 Annotated
是唯一的,在处理用户定义的类型提示时也不一定要假定。
那么,是否有更好的方法来进行此测试?
这个怎么样?
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 级别的实现细节。