从 typing.Annotated 中递归剥离类型注解的教程

霞舞
发布: 2025-11-05 12:17:01
原创
224人浏览过

从 typing.Annotated 中递归剥离类型注解的教程

本文深入探讨了在python中处理嵌套 `typing.annotated` 类型时,如何优雅地提取其原始“裸”类型签名的挑战。通过介绍一种基于类型树递归遍历的解决方案,我们展示了如何利用 `typing` 模块的 `get_origin` 和 `get_args` 函数,构建一个通用且健壮的工具,以准确地从复杂类型结构中移除所有 `annotated` 注解,还原其纯粹的类型表示,而无需依赖复杂的正则表达式

理解 typing.Annotated 及其挑战

typing.Annotated 是 Python 3.9 引入的一个强大特性,它允许开发者为类型提示附加运行时可访问的元数据。这对于需要额外信息(如验证规则、文档字符串、依赖注入提示等)的场景非常有用。例如,我们可以定义一个带有描述的 3D 点类型:

from typing import Annotated, tuple, list

Point3D = Annotated[tuple[float, float, float], "A 3D Point"]
Points = Annotated[list[Point3D], "A collection of points"]
登录后复制

然而,当这些带有注解的类型被嵌套使用时,它们的 repr() 表示会包含所有注解信息,这在某些情况下可能不是我们期望的。例如,直接打印 Points 会得到如下输出:

typing.Annotated[list[typing.Annotated[tuple[float, float, float], 'A 3D Point']], 'A collection of points']
登录后复制

如果我们只想获取其纯粹的类型结构,即 list[tuple[float, float, float]],而不包含任何注解信息,直接使用 typing.get_args() 往往不足以解决问题。例如,get_args(Points)[0] 会返回 list[typing.Annotated[tuple[float, float, float], 'A 3D Point']],内部的 Annotated 仍然存在。

解决方案:递归遍历类型树

要彻底剥离所有 Annotated 注解,我们需要一种能够深入到类型结构内部,识别并处理每一个 Annotated 节点的机制。最有效的方法是递归地遍历整个类型树。Python 的 typing 模块提供了 get_origin() 和 get_args() 函数,它们是实现这一目标的关键。

  • get_origin(type_object): 返回一个泛型类型(如 list[int])的原始类型(如 list)。对于非泛型类型,它返回 None。
  • get_args(type_object): 返回一个泛型类型的所有类型参数(如 list[int] 的 (int,))。对于非泛型类型,它返回空元组 ()。

利用这两个函数,我们可以构建一个递归函数来遍历类型对象,并在遇到 Annotated 类型时,将其替换为其第一个类型参数(即实际类型),然后继续递归处理。

实现 convert_annotated_to_bare_types 函数

以下是实现此功能的 Python 函数:

百度文心百中
百度文心百中

百度大模型语义搜索体验中心

百度文心百中 22
查看详情 百度文心百中
from typing import Annotated, get_args, get_origin, TypeVar, Union

# 示例类型定义
Point3D = Annotated[tuple[float, float, float], "A 3D Point"]
Points = Annotated[list[Point3D], "A collection of points"]
ComplexType = Annotated[Union[Point3D, list[float]], "A complex type example"]

def convert_annotated_to_bare_types(type_object: type):
  """
  递归地将类型对象中所有 typing.Annotated 节点转换为其裸类型。

  Args:
    type_object: 待处理的类型对象。

  Returns:
    移除了所有 Annotated 注解的裸类型对象。
  """
  origin, args = get_origin(type_object), get_args(type_object)

  # 1. 基础情况:如果不是泛型类型(没有 origin),直接返回
  if origin is None:
    return type_object

  # 2. 处理 Annotated 类型:
  #    如果是 Annotated,取其第一个参数(即实际类型),并递归处理
  if origin is Annotated:
    bare_type = get_args(type_object)[0]
    return convert_annotated_to_bare_types(bare_type)

  # 3. 处理其他泛型类型:
  #    递归处理所有类型参数,然后使用原始类型和处理后的参数重建泛型
  converted_args = [
    convert_annotated_to_bare_types(arg) for arg in args
  ]

  # 注意:对于像 TypeVar 这样的特殊类型,get_origin 会返回 TypeVar 本身,
  # 但它没有 args,或者 args 已经被处理过。
  # 这里的 origin(*converted_args) 语法适用于所有常见的泛型构造。
  # 对于 TypeVar,get_origin 返回其自身,get_args 返回空,
  # 所以 TypeVar() 依然是 TypeVar。
  try:
      return origin[*converted_args]
  except TypeError:
      # 某些特殊类型(如 TypeVar)可能不支持通过 origin[*args] 方式构造,
      # 或者其 args 经过处理后不再符合原始类型的构造要求。
      # 在这种情况下,我们尝试返回原始类型本身,或者根据具体情况进行更精细的处理。
      # 对于本教程的 Annotated 剥离目的,如果遇到构造失败,
      # 通常意味着这是一个不应被进一步解构的原子类型(如 TypeVar),
      # 或者参数列表已不匹配。
      # 简单起见,这里可以返回原始的 type_object,但更严谨可能需要根据 origin 判断。
      # 针对 TypeVar,get_origin(TypeVar('T')) 会返回 TypeVar('T') 本身,
      # get_args 会返回 (),所以 converted_args 也是 ()。
      # origin[*()] 也就是 TypeVar('T')(),这会报错。
      # 因此,对于 TypeVar 或类似情况,需要特殊处理。
      if isinstance(origin, TypeVar): # 如果 origin 本身就是 TypeVar
          return type_object # 直接返回原始 TypeVar

      # 否则,尝试返回 origin,或者抛出异常以便调试
      return origin 
登录后复制

函数解析

  1. 基础情况 (origin is None): 如果 type_object 不是泛型(例如 int, str, float 或一个自定义类),get_origin() 将返回 None。在这种情况下,没有注解需要剥离,直接返回 type_object。
  2. 处理 Annotated 类型 (origin is Annotated): 如果 type_object 是一个 Annotated 类型,get_origin() 将返回 typing.Annotated。根据 Annotated 的定义,其第一个参数是实际的类型。我们通过 get_args(type_object)[0] 提取这个实际类型,然后对它进行递归调用 convert_annotated_to_bare_types,确保内部可能存在的 Annotated 也能被处理。
  3. 处理其他泛型类型: 对于像 list, dict, Union 等其他泛型类型,我们首先递归地处理它们的所有类型参数 (args)。这确保了无论注解在类型结构的哪个层级,都能被捕获。处理完所有参数后,我们使用 origin(*converted_args) 语法来重建这个泛型类型。例如,如果 origin 是 list 且 converted_args 是 (int,),它将重建为 list[int]。
  4. 特殊情况处理 (TypeVar): 在 Python 类型系统中,TypeVar 是一种特殊类型,get_origin(TypeVar('T')) 会返回 TypeVar('T') 本身,而 get_args 返回空元组。直接使用 origin[*converted_args](即 TypeVar('T')())会导致 TypeError。因此,对于 TypeVar 类型的 origin,我们直接返回原始的 type_object。这确保了类型变量不会被错误地解构。

示例用法

让我们使用前面定义的 Points 和 ComplexType 来测试这个函数:

# 原始类型
print(f"原始 Points 类型: {Points}")
print(f"原始 ComplexType 类型: {ComplexType}")

# 剥离注解后的类型
bare_points = convert_annotated_to_bare_types(Points)
print(f"剥离注解后的 Points: {bare_points}")

bare_complex_type = convert_annotated_to_bare_types(ComplexType)
print(f"剥离注解后的 ComplexType: {bare_complex_type}")

# 验证结果
# 期望输出: list[tuple[float, float, float]]
# 实际输出: list[tuple[float, float, float]] (取决于 Python 版本和内部表示)
# 注意:在某些 Python 版本中,tuple[float, float, float] 可能显示为 typing.Tuple[float, float, float]
# 但其语义是相同的。
登录后复制

运行上述代码,你将看到如下输出(可能因Python版本略有差异):

原始 Points 类型: typing.Annotated[list[typing.Annotated[tuple[float, float, float], 'A 3D Point']], 'A collection of points']
原始 ComplexType 类型: typing.Annotated[typing.Union[typing.Annotated[tuple[float, float, float], 'A 3D Point'], list[float]], 'A complex type example']
剥离注解后的 Points: list[tuple[float, float, float]]
剥离注解后的 ComplexType: typing.Union[tuple[float, float, float], list[float]]
登录后复制

这完美地实现了我们的目标,从深层嵌套的 Annotated 类型中提取了纯粹的类型签名。

注意事项与总结

  • 运行时处理: 此解决方案是在运行时动态处理类型对象的。它不会修改源代码或类型提示的定义,而是在需要时提供一个“去注解”的视图。
  • 健壮性: 相比于使用正则表达式,这种基于 typing 模块内置函数的递归遍历方法更加健壮和准确。它能够正确处理各种复杂的泛型结构和嵌套层级,而正则表达式往往难以覆盖所有边缘情况且容易出错。
  • Annotated 的保留: 原始问题中提到,Annotated 的内容仍需在其他地方显示。此方法只生成一个“裸”类型的新对象,原始的 Annotated 类型定义保持不变,确保了其他需要注解信息的场景不受影响。
  • 性能: 对于大多数实际应用场景,类型树的深度和广度通常有限,因此这种递归方法的性能开销可以忽略不计。

通过这种递归遍历的方法,我们能够有效地从复杂的 typing.Annotated 结构中提取出其核心的类型信息,为那些需要纯粹类型签名的场景提供了优雅而可靠的解决方案。

以上就是从 typing.Annotated 中递归剥离类型注解的教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号