在Python的类型提示体系中,Annotated是一个可以让我们为类型标注附加额外元数据的重要工具,它定义在typing模块中,能够在不影响类型检查的前提下,为变量、函数参数、返回值等添加自定义的补充信息,这些信息在很多场景下都能发挥重要作用,比如参数校验、序列化规则定义、接口文档生成等。提取Annotated中的元数据信息,是发挥这些附加信息价值的关键步骤。

Annotated的基本使用方式
Annotated的语法结构为Annotated[基础类型, 元数据1, 元数据2, ...],其中第一个参数是基础的类型标注,后面的参数都是附加的元数据,元数据可以是任意类型的对象,比如字符串、数字、自定义类的实例等。下面是一个简单的使用示例:
from typing import Annotated # 为int类型附加最小值和最大值的元数据 Age = Annotated[int, 0, 120] # 为str类型附加长度限制的元数据 Username = Annotated[str, 6, 20]
提取Annotated元数据的核心方法
要提取Annotated中的元数据,我们需要借助typing模块提供的get_type_hints函数,以及typing_extensions或者Python 3.11+内置的inspect模块中相关的类型解析工具。核心思路是先获取目标对象的类型提示,然后判断类型提示是否为Annotated类型,如果是则从中拆分出基础类型和元数据部分。
基础提取示例
下面的代码演示了如何提取函数参数中Annotated类型的元数据:
from typing import Annotated, get_type_hints
from typing_extensions import get_origin, get_args
def get_annotated_metadata(hint):
# 判断类型提示是否为Annotated类型
if get_origin(hint) is Annotated:
# 获取Annotated的所有参数,第一个是基础类型,后面的都是元数据
args = get_args(hint)
base_type = args[0]
metadata = args[1:]
return base_type, metadata
return hint, ()
# 定义带Annotated类型参数的函数
def register_user(name: Annotated[str, "用户名", 2, 10], age: Annotated[int, "年龄", 0, 150]):
pass
# 获取函数的类型提示
hints = get_type_hints(register_user)
for param_name, param_hint in hints.items():
base_type, metadata = get_annotated_metadata(param_hint)
print(f"参数名:{param_name}")
print(f"基础类型:{base_type}")
print(f"元数据:{metadata}")
print("-" * 30)
运行上述代码,会输出每个参数的基础类型和对应的元数据信息,通过这种方式我们可以快速拿到Annotated中附加的所有补充内容。
提取类属性中的Annotated元数据
除了函数参数,类的属性也可以使用Annotated类型标注,提取方式和函数参数类似,只是获取类型提示的对象变成了类本身:
from typing import Annotated, get_type_hints
from typing_extensions import get_origin, get_args
class User:
# 为类属性附加元数据
username: Annotated[str, "用户名", 6, 20]
age: Annotated[int, "年龄", 0, 120]
# 获取类的类型提示
hints = get_type_hints(User)
for attr_name, attr_hint in hints.items():
if get_origin(attr_hint) is Annotated:
args = get_args(attr_hint)
base_type = args[0]
metadata = args[1:]
print(f"属性名:{attr_name}")
print(f"基础类型:{base_type}")
print(f"元数据:{metadata}")
提取元数据的注意事项
- Python 3.11之前的版本中,get_origin和get_args函数需要从typing_extensions模块导入,Python 3.11及以上版本可以直接从typing模块导入这两个函数。
- 如果类型提示不是Annotated类型,直接调用get_args可能会得到不符合预期的结果,所以提取前一定要先通过get_origin判断类型是否为Annotated。
- 元数据的顺序和定义时一致,提取后可以按照定义时的约定来解析不同位置的元数据含义,比如约定第一个元数据是描述信息,第二个是最小值,第三个是最大值。
- get_type_hints函数默认会解析所有可解析的类型提示,如果遇到无法解析的类型标注可能会抛出异常,使用时可以根据需要添加异常处理逻辑。
实际应用场景示例
我们可以结合提取到的元数据实现简单的参数校验功能,比如校验函数参数的取值是否符合Annotated中定义的规则:
from typing import Annotated, get_type_hints
from typing_extensions import get_origin, get_args
def validate_params(func):
def wrapper(*args, **kwargs):
hints = get_type_hints(func)
# 获取函数的参数名列表
import inspect
sig = inspect.signature(func)
param_names = list(sig.parameters.keys())
# 校验位置参数
for i, arg in enumerate(args):
param_name = param_names[i]
param_hint = hints.get(param_name)
if param_hint and get_origin(param_hint) is Annotated:
base_type, metadata = get_args(param_hint)[0], get_args(param_hint)[1:]
# 简单校验类型
if not isinstance(arg, base_type):
raise TypeError(f"参数{param_name}类型错误,期望{base_type},实际{type(arg)}")
# 如果是int类型,校验范围
if base_type is int and len(metadata) >= 2:
min_val, max_val = metadata[0], metadata[1]
if not (min_val <= arg <= max_val):
raise ValueError(f"参数{param_name}值超出范围,应在{min_val}到{max_val}之间")
return func(*args, **kwargs)
return wrapper
@validate_params
def add_user(age: Annotated[int, 0, 120]):
print(f"添加用户,年龄:{age}")
# 正常调用
add_user(25)
# 调用会抛出异常,因为年龄超出范围
# add_user(130)
通过上述示例可以看到,提取Annotated中的元数据后,我们可以灵活地实现各种扩展功能,让类型提示的价值得到更大程度的发挥。