python annotated的教程

python Annotated 简单和复杂用法
 
 
 
 
Annotated 是 Python 3.9 引入的一个特性(属于 typing 模块) [1]。它的核心作用是:为类型提示(Type Hints)添加“额外的信息”(元数据/Metadata)。
这些额外的信息不会影响 Python 的运行,但可以被第三方工具(如 Pydantic、FastAPI、FastAPI 校验器)读取,用来做数据校验、文档生成或依赖注入。

1. 简单用法:仅作代码说明(无功能性)
最简单的用法是把 Annotated 当作一种更规范的注释。
  • 语法:Annotated[类型, 元数据1, 元数据2, ...] [1]
  • 示例:
python
from typing import Annotated

# 告诉看代码的人:x 是整数,且代表价格(单位是元)
price: Annotated[int, "price in CNY"] = 100

# 告诉看代码的人:name 是字符串,且不能包含空格
username: Annotated[str, "no spaces allowed"] = "alex_green"
请谨慎使用此类代码。
  • 效果:Python 解释器只看第一个参数 intstr [1]。后面的字符串只是“备注”,不影响程序运行。

2. 进阶用法:配合 Pydantic 做数据校验
在实际开发(如 FastAPI 项目)中,Annotated 最常用于限制数据的范围或格式。
  • 示例:限制用户的年龄必须在 18 到 120 岁之间。
python
from pydantic import BaseModel, Field
from typing import Annotated

# 定义一个自定义类型:必须是 int,且范围在 18-120
AdultAge = Annotated[int, Field(ge=18, le=120)]

class User(BaseModel):
    name: str
    age: AdultAge  # 使用自定义的 Annotated 类型

# 正确数据
user1 = User(name="张三", age=25) 

# 错误数据:会直接报错(ValidationError)
user2 = User(name="李四", age=10) 
请谨慎使用此类代码。

3. 复杂用法:在 FastAPI 中做依赖注入与参数校验
在 FastAPI 中,Annotated 可以大幅减少重复代码,让函数签名非常干净。
复杂场景:统一的数据库连接 + 分页校验
python
from fastapi import FastAPI, Depends, Query
from typing import Annotated

app = FastAPI()

# 1. 模拟一个数据库连接依赖
def get_db():
    db = "Database_Connection"
    try:
        yield db
    finally:
        pass

# 2. 定义复杂的 Annotated 类型
# Database: 既是字符串类型,又自动注入数据库连接
Database = Annotated[str, Depends(get_db)]

# PageParam: 既是整数类型,又有默认值 1,且必须大于等于 1
PageParam = Annotated[int, Query(default=1, ge=1)]

@app.get("/items")
async def read_items(
    db: Database,        # 复杂用法:自动注入
    page: PageParam,     # 复杂用法:自动校验 >= 1
    size: Annotated[int, Query(le=100)] = 20 # 限制单页最多 100 条
):
    return {"db": db, "page": page, "size": size}
请谨慎使用此类代码。

4. 终极复杂用法:自己编写工具读取 Annotated
你也可以自己写代码,去读取变量里的元数据,实现自定义的业务逻辑。
  • 示例:通过读取元数据,自动将变量翻译成中文。
python
from typing import Annotated, get_type_hints

# 1. 定义带中文标签的变量
class Profile:
    name: Annotated[str, "姓名"] = "张三"
    age: Annotated[int, "年龄"] = 28

# 2. 编写读取函数
def print_chinese_labels(cls):
    # get_type_hints 可以获取类中所有变量的类型提示
    hints = get_type_hints(cls, include_extras=True)
    
    for var_name, hint in hints.items():
        # hint.__metadata__ 可以拿到 Annotated 后面的所有参数 [1]
        label = hint.__metadata__[0]
        print(f"变量名: {var_name} -> 中文标签: {label}")

# 3. 运行
print_chinese_labels(Profile)
请谨慎使用此类代码。
输出结果:
text
变量名: name -> 中文标签: 姓名
变量名: age -> 中文标签: 年龄
请谨慎使用此类代码。

💡 核心总结
用法级别核心作用常见场景
简单用法 纯作注释,提高代码可读性 [1] 团队协作,解释变量单位(如 cm, kg
进阶用法 类型复用,配合 Pydantic 校验数据 限制字符串长度、限制数字大小
复杂用法 配合 Web 框架,实现依赖注入 FastAPI 中的 DependsQueryPath 简化
终极用法 利用 __metadata__ 属性,手写框架 自定义权限校验、自动表单生成器 [1]
如果您目前正在开发具体的项目,我可以为您:
  • 演示如何用 Annotated 重构您现有的 FastAPI/Pydantic 代码
  • 展示如何用它来做 精细的字符串正则匹配(Regex)校验
你想针对哪一个具体场景深入了解呢?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Annotated 重构您现有的 FastAPI/Pydantic 代码
 
 
 
 
为了让你最直观地感受到 Annotated 的威力,我们先来看一段传统的 FastAPI/Pydantic 代码,然后再看用 Annotated 重构后的样子。
通过对比,你会发现代码不仅变干净了,而且可复用性极高。

❌ 传统写法(重构前)
在没有使用 Annotated 之前,我们的数据校验和依赖注入都堆积在函数的参数列表里,或者在 Pydantic 模型里重复编写。
python
from fastapi import FastAPI, Query, Header, Depends
from pydantic import BaseModel, Field

app = FastAPI()

# Pydantic 模型
class User(BaseModel):
    # 如果多个模型都要校验用户名,这些 Field 限制就要到处复制粘贴
    username: str = Field(..., min_length=3, max_length=20, pattern=r"^[a-zA-Z0-9_-]+$")
    age: int = Field(..., ge=18, le=100)

# 模拟一个数据库依赖
def get_db():
    return "DB_CONNECTION"

@app.get("/users")
def get_users(
    # 参数列表非常冗长,可读性差
    page: int = Query(1, ge=1, description="页码"),
    size: int = Query(10, ge=1, le=100, description="每页条数"),
    user_token: str = Header(..., alias="X-User-Token"),
    db=Depends(get_db)
):
    return {"page": page, "size": size, "token": user_token, "db": db}
请谨慎使用此类代码。
传统写法的问题:
  1. pagesize 的校验规则如果多个接口都要用,必须每个接口复制一遍。
  2. db=Depends(get_db) 这种写法不符合标准 Python 语法类型提示,IDE(如 PyCharm/VS Code)无法提供完美的类型补全。

使用 Annotated 重构(重构后)
重构的核心思想是:把“类型”和“校验规则/依赖注入”打包成一个独立的“新类型”。
python
from fastapi import FastAPI, Query, Header, Depends
from pydantic import BaseModel, Field
from typing import Annotated

app = FastAPI()

# ----------------------------------------------------------------
# 1. 抽离并定义可复用的“业务类型”(将规则与类型绑定)
# ----------------------------------------------------------------

# 校验规则:用户名必须3-20位,符合正则
UsernameType = Annotated[str, Field(min_length=3, max_length=20, pattern=r"^[a-zA-Z0-9_-]+$")]
# 校验规则:成年人年龄
AdultAgeType = Annotated[int, Field(ge=18, le=100)]

# 校验规则:分页页码(默认1,必须 >= 1)
PageQuery = Annotated[int, Query(default=1, ge=1, description="页码")]
# 校验规则:分页大小(默认10,1-100之间)
SizeQuery = Annotated[int, Query(default=10, ge=1, le=100, description="每页条数")]

# 请求头:自动获取特定的 Header
UserTokenHeader = Annotated[str, Header(alias="X-User-Token")]

# 依赖注入:数据库连接
def get_db():
    return "DB_CONNECTION"
# 完美解决:db 变量既是 str 类型,又自动注入 get_db 依赖
DatabaseDep = Annotated[str, Depends(get_db)]


# ----------------------------------------------------------------
# 2. 在 Pydantic 模型中使用重构后的类型
# ----------------------------------------------------------------
class User(BaseModel):
    username: UsernameType  # 极其干净,规则在上面统一管理
    age: AdultAgeType


# ----------------------------------------------------------------
# 3. 在 FastAPI 路由中使用重构后的类型
# ----------------------------------------------------------------
@app.get("/users")
def get_users(
    page: PageQuery,        # 语义清晰:这就是一个分页页码
    size: SizeQuery,        # 语义清晰:这就是一个分页大小
    user_token: UserTokenHeader,
    db: DatabaseDep         # IDE 现在能完美识别 db 是字符串类型并给与提示
):
    return {"page": page, "size": size, "token": user_token, "db": db}
请谨慎使用此类代码。

💡 重构带来的 3 大好处
  1. 绝对的可复用性:如果新建一个 Admin 模型也有用户名,直接写 admin_name: UsernameType 即可,修改规则只需要改一处。
  2. 函数签名极度简化:get_users 函数的参数列表现在只剩下 变量名: 类型,阅读代码时一眼就能看懂接口需要什么数据。
  3. 更强的类型推导:在使用 db: DatabaseDep 时,Python 知道 db 本质上是个 str,当你写 db. 时,编辑器会自动弹出字符串的方法提示(如 split, upper 等)。
想看看怎么把这套 Annotated 规则应用到你目前正在写的具体项目/接口中吗?你可以把你的代码片段发给我,我们现场重构!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
展示如何用它来做 精细的字符串正则匹配(Regex)校验
 
 
 
 
在 Python 中,结合 AnnotatedPydantic(FastAPI 的底层数据校验库),我们可以使用正则表达式(Regex)对字符串进行极其精细的校验。
在 Pydantic v2(目前最常用的版本)中,我们使用 Field(pattern=r"...") 来实现正则匹配。
下面为你展示 4 个实际开发中最常用的精细正则校验场景:

1. 完整重构代码示例
我们将常见的中国大陆手机号、强密码、车牌号和18位身份证号的正则校验打包成独立的 Annotated 类型:
python
from pydantic import BaseModel, Field, ValidationError
from typing import Annotated

# ----------------------------------------------------------------
# 1. 定义精细的正则 Annotated 类型
# ----------------------------------------------------------------

# 手机号:1开头,第二位是3-9,后面加9位数字
ChinaMobile = Annotated[
    str, 
    Field(pattern=r"^1[3-9]\d{9}$", description="中国大陆手机号")
]

# 强密码:必须包含大小写字母、数字和特殊字符,长度 8-20 位
StrongPassword = Annotated[
    str, 
    Field(
        pattern=r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,20}$",
        description="强密码:需包含大小写字母、数字、特殊字符,8-20位"
    )
]

# 车牌号:支持传统蓝色车牌和新能源绿色车牌
ChinaLicensePlate = Annotated[
    str,
    Field(
        pattern=r"^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳试]$",
        description="中国车牌号"
    )
]

# ----------------------------------------------------------------
# 2. 在 Pydantic 模型中应用
# ----------------------------------------------------------------
class UserRegisterForm(BaseModel):
    username: str
    phone: ChinaMobile        # 应用手机号验证
    password: StrongPassword  # 应用强密码验证
    car_id: ChinaLicensePlate # 应用车牌验证

# ----------------------------------------------------------------
# 3. 测试校验效果
# ----------------------------------------------------------------

# 成功案例
try:
    valid_user = UserRegisterForm(
        username="Alex",
        phone="13812345678",
        password="Password123!", # 包含大小写、数字、特殊字符
        car_id="粤B12345"
    )
    print("✅ 验证成功:数据完全合规!")
except ValidationError as e:
    print(e.errors())

print("\n" + "="*50 + "\n")

# 失败案例(故意输错)
try:
    invalid_user = UserRegisterForm(
        username="Bob",
        phone="12345678901",    # ❌ 错误:不是1开头的有效手机号
        password="123456",       # ❌ 错误:太短且没有大小写特殊字符
        car_id="错A12345"        # ❌ 错误:开头省份简称不对
    )
except ValidationError as e:
    print("❌ 验证失败,详细错误信息如下:")
    for error in e.errors():
        print(f"字段 [{error['loc'][0]}]: {error['msg']}")
请谨慎使用此类代码。

2. 运行结果输出
当你运行上述代码时,失败案例会精准触发 Pydantic 的正则拦截,并输出易读的错误提示:
text
✅ 验证成功:数据完全合规!

==================================================

❌ 验证失败,详细错误信息如下:
字段 [phone]: String should match pattern '^1[3-9]\d{9}$'
字段 [password]: String should match pattern '^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,20}$'
字段 [car_id]: String should match pattern '^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳试]$'
请谨慎使用此类代码。

💡 为什么用 Annotated 做正则校验最爽?
  1. 写一次,到处用:正则表达式通常又长又难读。如果把它写在 Annotated 中定义为 ChinaMobile,你在团队开发时,其他人只需要调用 phone: ChinaMobile,再也不用去背或者复制那段复杂的正则表达式了。
  2. 前后端提示统一:如果你把这个模型用在 FastAPI 的路由参数里,FastAPI 会自动把 Field 里的 patterndescription 生成到 Swagger 交互式 API 文档中(/docs),前端开发人员一眼就能看到输入限制。
你手头有什么特定的字符串格式(比如特定格式的订单号、固定电话、IP地址等)需要写正则校验吗?我可以帮你把写好的正则直接封装进 Annotated 里!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
langchain使用 annotated
 
 
 
 
在 LangChain 中,Annotated 是一个非常核心的特性。由于 LangChain 深度集成了 Pydantic v2,它广泛使用 Annotated 来实现自定义 Tool(工具)的参数校验以及结构化输出(Structured Output)的字段定义。
使用 Annotated 的最大好处是:你为参数写的注释、正则或描述,会被 LangChain 自动转化为大模型(LLM)看得懂的 Prompt 提示词,从而让模型调用工具时极其精准。
以下是 LangChain 中使用 Annotated 的 3 个核心实战场景:

1. 场景一:在 @tool 装饰器中精准定义工具参数(最常用)
当你定义一个工具让大模型调用时,大模型需要知道每个参数的含义。利用 Annotated + pydantic.Field,你可以直接把参数的“限制”和“描述”打包。
python
from langchain_core.tools import tool
from typing import Annotated
from pydantic import Field

# 使用 Annotated 定义精细的参数限制
# 1. 限制城市名称的格式
CityInput = Annotated[str, Field(description="中国城市名称,必须是中文,例如:北京、上海")]
# 2. 限制查询的天数,必须在 1-7 天之内
DaysInput = Annotated[int, Field(ge=1, le=7, description="查询未来几天的天气预报,最多7天")]

@tool
def get_weather_forecast(city: CityInput, days: DaysInput = 1) -> str:
    """获取指定城市的天气预报。"""
    # 这里的 docstring(工具描述)和 Annotated 里的元数据
    # 会被 LangChain 自动提取并发送给大模型(如 GPT-4 / Claude)
    return f"正在查询 {city} 未来 {days} 天的天气:晴,25度。"

# 验证 LangChain 是否成功识别了这些复杂的元数据
print(get_weather_forecast.args)
请谨慎使用此类代码。
输出结果(LangChain 自动生成的工具 schema):
json
{
  "city": {"title": "City", "description": "中国城市名称,必须是中文,例如:北京、上海", "type": "string"},
  "days": {"title": "Days", "description": "查询未来几天的天气预报,最多7天", "maximum": 7, "minimum": 1, "default": 1, "type": "integer"}
}
请谨慎使用此类代码。

2. 场景二:结合正则(Regex)防止大模型传错参数
有时候模型会胡乱生成参数(比如提取订单号时少了前缀)。我们可以用 Annotated 加上正则校验。如果模型生成的参数不符合正则,LangChain 的 Pydantic 校验层会直接拦截并报错,你甚至可以捕获这个错误让模型重新生成(重试)。
python
from langchain_core.tools import tool
from typing import Annotated
from pydantic import Field

# 严格限制订单号格式:必须是 "ORDER-" 开头,后面跟 8 位数字
StrictOrderID = Annotated[
    str, 
    Field(pattern=r"^ORDER-\d{8}$", description="系统订单号,格式必须为 ORDER-12345678")
]

@tool
def refund_order(order_id: StrictOrderID) -> str:
    """对指定的订单执行退款操作。"""
    return f"订单 {order_id} 退款成功!"

# 如果大模型试图传入 "12345" 或者 "ORDER-abc",Pydantic 会在底层直接拦截,保护业务安全
请谨慎使用此类代码。

3. 场景三:在 with_structured_output 中定义结构化提取结果
在需要让 LLM 提取结构化数据(比如情感分析、信息抽取)时,我们通常要定义一个 Pydantic 类。在类中使用 Annotated 可以让代码看起来非常优雅。
python
from langchain_core.pydantic_v1 import BaseModel # 注:根据LangChain版本,也可能是 from pydantic import BaseModel
from typing import Annotated
from pydantic import Field
from langchain_openai import ChatOpenAI

# 1. 使用 Annotated 定义抽取的标准字段
UserSentiment = Annotated[str, Field(description="用户的情感倾向,只能是 '正面', '负面' 或 '中性'")]
KeyKeywords = Annotated[list[str], Field(description="从文本中提取的关键词列表,最多3个")]

class SocialMediaAnalysis(BaseModel):
    sentiment: UserSentiment
    keywords: KeyKeywords

# 2. 绑定模型进行结构化输出
llm = ChatOpenAI(model="gpt-4o")
structured_llm = llm.with_structured_output(SocialMediaAnalysis)

# 3. 执行提取
result = structured_llm.invoke("这家店的服务态度太差了,气死我了,避雷!")
print(result.sentiment)  # 输出: 负面
print(result.keywords)   # 输出: ['服务态度', '差', '避雷']
请谨慎使用此类代码。

💡 总结:为什么 LangChain 开发必用 Annotated
  1. 天然的 Prompt:你写的 Field(description="...") 不需要你手动拼接进 Prompt 里,LangChain 会自动将其转化为 JSON Schema 传给大模型。
  2. 前后端守门员:大模型(LLM)本质上是概率模型,容易出现幻想或格式错误。Annotated 结合 Pydantic 可以在代码最前端直接把不合规的输入拦下来,防止脏数据进入你的数据库或后端系统。
你目前是在开发 Agent(智能体工具调用),还是在做 数据提取(结构化输出) 呢?我可以根据你正在使用的特定 LLM(如 OpenAI、智谱、Ollama 等)为你写一个针对性的完整运行示例!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
langgraph里面对annotated的用法
 
 
 
 
在 LangGraph 中,Annotated 的用法不仅延续了 Pydantic 的数据校验,更承担了一项绝对核心的框架级功能:Reducer(状态合并函数)
在 LangGraph 中,由于多个节点(Nodes)可能会同时或先后修改图的状态(State),你必须告诉 LangGraph 当多个节点写入同一个变量时,该如何合并它们的数据。这就需要用到 Annotated
以下是 LangGraph 中 Annotated 的 3 个最核心、最常用的实战场景:

1. 核心用法:用 Annotated 实现消息列表的自动追加(最常用)
在构建对话机器人(Chatbot)时,我们需要维护一个 messages 列表。
  • 痛点:如果不做特殊处理,节点 B 写入的 messages 会直接覆盖掉节点 A 写入的 messages
  • 解法:使用 Annotated[list, add_messages]。它告诉 LangGraph:“不要覆盖这个列表,每当有新消息进来时,使用 add_messages 函数将新消息追加(Append)到原列表后面”。
python
from typing import Annotated, Sequence
from typing_extensions import TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages
from langgraph.graph import StateGraph, START, END

# 1. 定义图的状态(State)
class State(TypedDict):
    # 【核心】:使用 Annotated 绑定 add_messages 合并函数
    messages: Annotated[list[BaseMessage], add_messages]
    user_id: str  # 没有 Annotated 的变量,后续写入会直接“覆盖”原值

# 2. 定义节点
def chatbot_node(state: State):
    # 这里返回的新消息,会被 add_messages 自动追加到 state["messages"] 后面
    return {"messages": [("assistant", "你好!我是你的 AI 助手。")]}

# 3. 构建图
builder = StateGraph(State)
builder.add_node("chatbot", chatbot_node)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)
graph = builder.compile()
请谨慎使用此类代码。

2. 进阶用法:自定义复杂的 Reducer(状态合并逻辑)
除了官方自带的 add_messages,你完全可以自己写一个合并函数,并放入 Annotated 中。
  • 场景:假设你在做一个网络爬虫或多 Agent 研究员(Research Agent),多个并发的节点会同时搜集到不同的网页链接(URLs),你希望把它们合并到一个列表中,并且自动去重。
python
from typing import Annotated
from typing_extensions import TypedDict

# 1. 自定义一个 Reducer 函数
# 它接收两个参数:现有的值 (current) 和 新写入的值 (update)
def merge_and_distinct_urls(current: list[str], update: list[str]) -> list[str]:
    if current is None:
        current = []
    # 合并两个列表,并使用 dict.fromkeys 或 set 去重,同时保持顺序
    combined = current + update
    return list(dict.fromkeys(combined))

# 2. 在 State 中使用它
class ResearchState(TypedDict):
    topic: str
    # 【核心】:当有新 URL 写入时,自动调用自定义的去重合并函数
    collected_urls: Annotated[list[str], merge_and_distinct_urls]

# 3. 模拟两个并发节点写入
def search_google(state: ResearchState):
    return {"collected_urls": ["https://a.com", "https://b.com"]}

def search_bing(state: ResearchState):
    # 注意:这里也搜到了 b.com
    return {"collected_urls": ["https://b.com", "https://c.com"]}

# 最终 LangGraph 自动合并后的 collected_urls 将会是:
# ["https://a.com", "https://b.com", "https://c.com"] (b.com 只出现一次)
请谨慎使用此类代码。

3. 复杂用法:在 State 内部嵌套使用 Pydantic + Annotated
在大型复杂项目中,LangGraph 的 State 可以由 Pydantic 模型来定义(而不仅仅是 TypedDict)。这样你既能享受 Pydantic 的强类型数据校验,又能享受 LangGraph 的 Reducer 合并机制。
python
from pydantic import BaseModel, Field
from typing import Annotated
from langgraph.graph import StateGraph

# 1. 自定义一个数字累加的 Reducer
def accumulate_tokens(current: int, update: int) -> int:
    return (current or 0) + update

# 2. 用 Pydantic 定义复杂 State
class PydanticState(BaseModel):
    session_id: str
    # 结合 Pydantic 的 Field 校验和大模型的描述 + LangGraph 的 Reducer
    total_tokens_used: Annotated[
        int, 
        accumulate_tokens, # 👈 这是 LangGraph 的 Reducer:自动累加 Token
        Field(default=0, description="整个会话消耗的 Token 总数") # 👈 这是 Pydantic 的校验/描述
    ]

# 使用这个 State 构建图
# builder = StateGraph(PydanticState)
请谨慎使用此类代码。
注:在 Annotated 中,LangGraph 非常聪明,它会自动识别出哪个参数是可执行的函数(比如 accumulate_tokens),并将其作为 Reducer 处理,其余的则留给 Pydantic。

💡 核心复习小结
在 LangGraph 中看到 Annotated,它 90% 的情况下都代表 “状态合并规则(Reducer)”:
  1. messages: Annotated[list, add_messages]:最标准的写法,让对话历史能够像滚雪球一样不断追加,而不是被覆盖。
  2. custom_var: Annotated[Type, my_reducer_func]:自定义复杂的合并逻辑(如数字相加、字典合并、列表去重、甚至只保留最新值)。
你目前是在从零构建一个 Chatbot(聊天机器人),还是在设计一个复杂的 Multi-Agent(多智能体协同系统) 呢?如果是多智能体,我们可以聊聊如何利用自定义 Reducer 在不同的 Agent 节点之间优雅地传递和合并表单数据!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
# PageParam: 既是整数类型,又有默认值 1,且必须大于等于 1 PageParam = Annotated[int, Query(default=1, ge=1)] 这句话什么意思
 
 
 
 
 
这句话的意思是:我们定义了一个名叫 PageParam 的“新类型”,它专门用来给接口的“页码”参数贴标签,并且给这个标签绑定了三条硬性规定。
我们可以把这句话拆成两部分来理解:
1. 左边 PageParam =
  • 意思:起一个好记的别名。
  • 作用:以后在写代码时,只要遇到需要写“页码”的地方,不需要每次都重复写一大堆限制条件,直接写 : PageParam 就可以了(实现代码复用)。
2. 右边 Annotated[int, Query(default=1, ge=1)]
这是核心,它用 Annotated(注脚工具)把类型和三条规定打包捆绑在一起:
  • 规定 ① int(必须是整数):传入的页码数据必须是整数,不能是小数(如 1.5)或字符串(如 "abc")。
  • 规定 ② Query(default=1...)(默认值是 1):如果前端(用户)在访问接口时没有传页码参数,系统会自动把页码当成第 1 页。
  • 规定 ③ ge=1(必须大于等于 1):ge 是 英文 Greater than or Equal to 的缩写。这意味着页码最小只能是 1,如果你敢传 0 或者 -5 这种负数,接口在最前端就会直接拦截并报错。

💡 实际应用对比
没用它之前(传统写法):
每个接口都要把这堆限制重写一遍,又长又丑:
python
@app.get("/books")
def get_books(page: int = Query(default=1, ge=1)): ...

@app.get("/users")
def get_users(page: int = Query(default=1, ge=1)): ...
请谨慎使用此类代码。
用了它之后(优雅写法):
直接调用定义好的 PageParam,代码变得像读英语一样清晰:
python
@app.get("/books")
def get_books(page: PageParam): ...

@app.get("/users")
def get_users(page: PageParam): ...
请谨慎使用此类代码。
如果您想了解,我还可以为您解释 Query(...) 里面除了 defaultge 之外,还能配置哪些高阶校验参数(比如最大值限制或别名设置),您想看看吗?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Query(...) 什么意思
 
 
 
 
Query(...)FastAPI 框架提供的一个专门用来控制和校验“URL查询参数”(Query Parameters)的工具函数。
所谓“查询参数”,就是网址(URL)中问号 ? 后面的那部分数据,比如 http://example.com 中的 page=1size=10
通过 Query(...),你可以向 FastAPI 声明这个参数的默认值、数据范围以及文档描述。

1. 拆解 Query() 里的核心参数
以你刚才看到的 Query(default=1, ge=1) 为例,它里面能放很多控制规则:
  • default=1默认值 [1]。如果用户访问网址时没写 ?page=1,系统会自动把 page 当作 1 来看待。
  • ge=1大于等于 1(Greater than or Equal to)。限制用户传进来的数字不能小于 1。

2. Query(...) 还能做什么?(高阶用法)
除了限制数字,Query() 还可以用来限制字符串、给参数起别名、甚至隐藏参数:
限制字符串长度和格式
python
# 限制搜索关键词:默认值为 None(选填),但如果填了,长度必须在 3 到 50 个字符之间
search: Annotated[str | None, Query(min_length=3, max_length=50)] = None
请谨慎使用此类代码。
给参数起别名(Alias)
有时候 Python 的变量命名规范(不能有连字符 -)和前端的要求冲突。
python
# 前端传过来的网址是 ?user-id=123
# 通过 alias,Python 代码里可以用符合规范的下划线变量 user_id 来接收
user_id: Annotated[str, Query(alias="user-id")]
请谨慎使用此类代码。
控制自动生成的 API 文档(Swagger)
FastAPI 会根据你的代码自动生成精美的交互式网页文档。你可以用 Query() 让文档更详细:
python
# description 的内容会直接显示在 API 文档上,告诉前端或测试人员这个参数是干嘛的
keyword: Annotated[str, Query(description="搜索商品的关键词,支持模糊匹配")]
请谨慎使用此类代码。

💡 一句话总结
在 FastAPI 中,写 page: int 只是告诉系统“这里需要一个整数”;而加上 Query(...),则是给这个整数立规矩(比如:不能是负数、不传就默认是1、在文档里怎么写描述等)。
除了 Query() 用于网址问号后面的参数,FastAPI 还有 Path()(用于网址路径里的参数)和 Header()(用于请求头) [1]。你想顺便了解一下 Path()Query() 的区别吗?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
post的数据 使用query吗
 
 
 
 
 
不用。POST 请求的数据通常不使用 Query
在 Web 开发和 FastAPI 中,POST 请求和 GET 请求传递数据的方式有本质的区别,它们对应着不同的接收工具:

1. 为什么 POST 不用 Query?
  • Query 是给 URL 问号后面的参数立规规矩的(例如 ?page=1)。这些数据是直接暴露在网址栏里的。
  • POST 请求的目的通常是提交“复杂或隐私的数据”(例如账号密码、发帖内容、复杂表单)。这些数据应该藏在请求体(Request Body)里面,而不是挂在网址后面。

2. POST 请求应该用什么?
在 FastAPI 中,针对 POST 请求提交的 JSON 数据,我们使用 Body(...) 或者直接定义一个 Pydantic 模型(BaseModel)。
💻 正确示例:POST 接收请求体数据
python
from fastapi import FastAPI, Body
from pydantic import BaseModel, Field
from typing import Annotated

app = FastAPI()

# 做法 A:使用 Pydantic 模型(最推荐、最常用)
class UserRegister(BaseModel):
    username: str
    password: Annotated[str, Field(min_length=6, description="密码至少6位")]

@app.post("/register")
def register_user(user: UserRegister): # 👈 自动识别为 POST 请求体 JSON 数据
    return {"message": f"用户 {user.username} 注册成功"}


# 做法 B:直接使用 Body() 接收单个纯文本/数字
@app.post("/update-score")
def update_score(
    # 👈 使用 Body 而不是 Query,代表这个数据要从请求体(JSON)里拿
    score: Annotated[int, Body(ge=0, le=100)] 
):
    return {"status": "success", "new_score": score}
请谨慎使用此类代码。

3. 唯一的特例:POST 也可以同时带 Query
虽然 POST 的主要数据放在请求体里,但技术上它可以同时在网址后面挂一点小参数。
  • 场景:你向服务器提交(POST)一篇很长文章的 JSON 内容,但同时你想在网址上指定要不要立刻发布(?publish=true)。
python
@app.post("/articles")
def create_article(
    article_content: UserRegister,          # 👈 没有标记或标记为 Body 的,从请求体(Body)拿数据
    publish: Annotated[bool, Query()] = True # 👈 标记为 Query 的,依然从网址问号后面拿数据
):
    return {"content": article_content, "is_published": publish}
请谨慎使用此类代码。

💡 核心总结
  • GET 请求 ➔ 数据在网址上 ➔ 用 Query(...)
  • POST 请求 ➔ 数据在请求体(暗盒)里 ➔ 用 Body(...)BaseModel
你目前是在写一个 POST 接口(比如登录、上传数据、提交表单) 吗?可以把你的业务场景告诉我,我帮你看看是用 BaseModel 还是 Body 最合适!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

posted on 2026-08-03 15:45  漫思  阅读(23)  评论(0)    收藏  举报

导航