【问题标题】:What is the right way to check if a type hint is annotated?检查类型提示是否已注释的正确方法是什么?
【发布时间】:2021-07-06 18:20:33
【问题描述】:

Python 3.9 引入了Annotated 类,它允许将任意元数据添加到类型提示中,例如,

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

据我所知,没有记录在案的方法可以做到这一点,并且有几种可能的方法,但似乎都不理想。

比较 typeAnnotated 不起作用,因为类型提示不是 Annotated 的实例:

>>> type(h["x"])
typing._AnnotatedAlias

所以我们必须这样做:

if type(h["x"]) is _AnnotatedAlias:
    ...

但是,鉴于 _AnnotatedAlias 中的前导下划线,这可能需要使用实现细节。

另一种选择是直接检查__metadata__属性:

if hasattr(h["x"], "__metadata__"):
    ...

但这假设__metadata__ 属性是Annotated 独有的,在处理用户定义的类型提示时也不一定能假设。

那么,有没有更好的方法来做这个测试?

【问题讨论】:

  • 我目前正在努力解决与此类似的问题 - 我无法为您提供答案,因为我无法提出比您的建议更好的建议。我相信目前没有官方方法来执行这种测试的原因是打字模块仍然是新的并且在每个python版本中都在迅速变化,所以不能保证API从版本到版本都保持不变版本。
  • 通常你会为此使用鸭子打字。因此,您只需使用 try/except 块,而不是显式检查。

标签: python type-hinting python-typing


【解决方案1】:

这个怎么样?

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 级别的实现细节。

【讨论】:

    猜你喜欢
    • 2020-02-20
    • 1970-01-01
    • 1970-01-01
    • 2012-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-16
    相关资源
    最近更新 更多