检查类型提示是否被注释的正确方法是什么?

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 其中看起来很理想。

无法将 typeAnnotated 进行比较,因为类型提示不是 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 级别的实现细节。