typing.Annotated
typing.Annotated
是 Python 类型系统中一个强大的工具,它允许开发者为类型声明添加额外元数据,这些元数据可以被库、框架或自定义代码读取和使用。
什么是 Annotated?
Annotated[T, metadata]
的基本语法表示一个类型 T
和与之关联的元数据。元数据可以是任意Python对象或对象集合。
from typing import Annotated
# 基本用法:为字符串类型添加描述性元数据
name: Annotated[str, "首字母必须大写"]
这个注解表明 name
是 str
类型,并且附带了一个说明:"首字母必须大写"。
核心概念
关键特性
- 运行时访问:通过
.__metadata__
属性可以访问附加的元数据 - 向后兼容:没有特殊处理逻辑的工具会忽略元数据,只将其视为基础类型
T
- 灵活扩展:元数据可以是任意Python对象,提供了极大的灵活性
实际应用场景
1. FastAPI 数据验证
FastAPI 广泛使用 Annotated
来声明参数验证规则:
from fastapi import FastAPI, Query
from typing import Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str, Query(max_length=50)]):
return {"q": q}
这里 Annotated
告诉 FastAPI 参数 q
应该是字符串类型,并且最大长度不超过50个字符。
优势
使用 Annotated
可以让参数声明更加简洁,避免了过长的函数签名,提高了代码的可读性。
2. Pydantic 模型验证
在 Pydantic 中,Annotated
可以替代繁琐的验证器写法:
from typing import Annotated
from pydantic import BaseModel, Field
# 传统方式需要自定义验证器
class OldModel(BaseModel):
x: int
# 需要额外的验证器方法...
# 使用 Annotated 的简洁方式
class NewModel(BaseModel):
x: Annotated[int, Field(lt=20, gt=2)]
这种方式利用 Pydantic 内置的能力来解析 Annotated
中的元数据并自动进行验证。
3. 类型延迟解析
在处理循环依赖时,Annotated
非常有用。Strawberry GraphQL 使用它来实现延迟类型解析:
from typing import Annotated
import strawberry
@strawberry.type
class User:
name: str
friends: Annotated[list["User"], strawberry.lazy("app.types")]
4. Typer CLI 应用
Typer 框架使用 Annotated
生成命令行帮助信息和参数验证:
from typing import Annotated
import typer
def main(name: Annotated[str, typer.Argument(help="你的名字")]):
print(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
5. 自定义元数据处理
你可以创建自己的框架来处理 Annotated
元数据:
from typing import Annotated, TypedDict, get_type_hints
def reducer(a: list, b: float) -> list:
return a + [b]
class State(TypedDict):
x: Annotated[list, reducer]
# 提取和处理元数据
metadata = {
name: typ.__metadata__[0]
for name, typ in get_type_hints(State, include_extras=True).items()
}
最佳实践
注意事项
- 工具支持:
Annotated
的效果取决于使用的库是否支持读取元数据 - 适度使用:不要过度使用复杂的元数据,保持代码的可读性
- 文档说明:为自定义的元数据使用方式提供清晰的文档
from fastapi import FastAPI, Query
from typing import Annotated
app = FastAPI()
@app.get("/users/")
async def read_users(
age: Annotated[int, Query(gt=0, lt=120, description="用户年龄")],
name: Annotated[str, Query(min_length=1, max_length=50)]
):
return {"name": name, "age": age}
from pydantic import BaseModel, Field
from typing import Annotated
class User(BaseModel):
username: Annotated[str, Field(min_length=3, max_length=20)]
email: Annotated[str, Field(pattern=r"^[^@]+@[^@]+\.[^@]+$")]
总结
typing.Annotated
是一个强大的类型注解工具,它通过为类型添加元数据来扩展Python类型系统的表达能力。虽然它本身不提供任何功能,但当与支持它的库(如FastAPI、Pydantic、Strawberry、Typer等)结合使用时,可以显著简化代码并提高开发效率。
在实际开发中,合理使用 Annotated
可以让你的代码更加声明式、简洁和易于维护,特别是在需要复杂验证、文档生成或自定义处理的场景中。