新闻中心

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

2025-11-05
浏览次数:
返回列表

从 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 函数:

VALL-E VALL-E

VALL-E是一种用于文本到语音生成 (TTS) 的语言建模方法

VALL-E 134 查看详情 VALL-E
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 中递归剥离类型注解的教程的详细内容,更多请关注其它相关文章!


# 正则表达式  # 在这种情况下  # 解决问题  # 为其  # 我们可以  # 是一种  # 是在  # 第一个  # 遍历  # 递归  # 递归函数  # 工具  # python  # 洪梅电子网站推广哪儿好  # 广州seo营销  # 营销推广的控制  # 英文文案外贸推广 营销  # 巩义网站建设开发有哪些  # 网站优化是指  # 徐汇营销推广地址  # 无锡网站推广靠谱吗  # 营销推广实训课方案总结  # 盐山做网站优化 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: Win10系统怎么查看已安装更新_Win10卸载有问题的更新补丁  AO3官网镜像链接 Archive of Our Own同人文在线浏览  拷贝漫画电脑版官网入口 拷贝漫画(PC版)在线直达  QQ邮箱登录官网首页 腾讯QQ邮箱网页入口  Win11怎么查看显卡显存 Win11显示适配器属性及专用视频内存查询  QQ邮箱网页版入口页面 QQ邮箱在线登录入口官网  大象笔记网页版入口 印象笔记网页版登录入口  漫蛙2正版漫画站 漫蛙2网页版快速访问入口  微信客户端如何收红包_微信客户端接收红包使用教程  J*aScript中高效管理与清空动态列表:避免循环陷阱  HTML5原生日期选择器与jQuery UI:实现日期选择器的联动与程序化控制  Typer应用中灵活处理命令行参数的令牌化与解析  mysql通配符支持数字匹配吗_mysql通配符能否用于数字匹配的解析  妖精漫画网页版登录入口免费_妖精漫画官网主页直接阅读漫画  星露谷物语官网入口 星露谷物语游戏官网入口  网易大神怎么保存别人动态的图片_网易大神动态图片保存方法  黑猫投诉统一入口官网 消费者权益保护投诉平台  新手怎么开始学化妆 零基础化妆入门教程  Win11输入法不见了怎么办_Windows11恢复语言栏显示方法  拼多多赚钱渠道_拼多多收益来源  2026春节假期时间安排 2026春节假日查询  LINQ to XML为何解析失败? 深入理解C# XDocument的异常处理  UC浏览器网页版登录入口官网 电脑版网址入口  我的世界官方游戏入口 我的世界官网平台直达链接  如何在J*a中使用Locale处理多语言环境  sublime怎么预览Markdown渲染效果_Markdown Preview插件 for sublime教程  Go语言中高效处理x-www-form-urlencoded表单数据  从J*aScript对象中精确提取指定属性的教程  J*a最大堆Heapify方法修复:索引计算与边界条件深度解析  PS5 Pro有点优势但不多! 《燕云十六声》PS5平台与PC性能画面对比  Spring Boot内嵌服务器与J*a EE全栈特性:选择与部署策略  c++ dfs和bfs代码 c++深度广度优先搜索算法  在J*a中如何隐藏复杂性_使用门面模式组织对象交互  邮编格式怎么匹配地址_根据邮编格式快速匹配详细地址的技巧  深入理解J*a合成构造器:何时以及为何阻止其生成  电脑安装程序提示“错误1722”怎么办_Windows Installer服务问题解决【教程】  excel怎么制作工资条 excel快速生成工资条的方法  如何在 Windows 11 中启动游戏手柄设置  J*a递归快速排序中静态变量导致数据累积问题的解决方案  J*aScript数据结构转换:将对象数组按类别分组  html两个JS只运行一个怎么办_让双JS在html中都运行方法【技巧】  铁路12306官网网页端快速入口 铁路12306官方首页登录教程  Sublime Text怎么设置垂直标尺_Sublime配置Rulers规范代码长度  《铁拳8》黑皮辣妹新实机:元气满满的18岁少女!  CSS Grid如何控制元素对齐_align-items与justify-items组合使用  composer 和 npm/yarn 在管理依赖方面有什么核心思想差异?  poki网页游戏推荐_poki免费游戏平台入口  MAC的“快捷指令”怎么同步到iPhone_MAC利用iCloud同步所有设备的自动化指令  腾讯QQ邮箱登录入口_QQ邮箱官方网站使用地址  Win11蓝牙耳机断连怎么解决 Win11蓝牙设置重新配对与驱动更新【技巧】 

搜索