首页
/ FastAPI 查询参数与字符串校验(Query 参数)完整指南:从 `Query()` 到 `AfterValidator`

FastAPI 查询参数与字符串校验(Query 参数)完整指南:从 `Query()` 到 `AfterValidator`

2026-09-07 18:22:36作者:柯茵沙

本文围绕 FastAPI 官方教程"Query 参数与字符串校验"(对应仓库文档 docs/fr/docs/tutorial/query-params-str-validations.md)展开,讲解如何在查询参数上声明额外的校验与元数据:长度约束、正则表达式、默认值、必填性、多值列表、别名、弃用标记,以及基于 Pydantic AfterValidator 的自定义校验。读完你将掌握基于 Annotated 的现代写法,并能在交互式 API 文档与 OpenAPI 模式中看到每一处声明的落地效果。

一、从最小的查询参数开始:为什么需要额外校验

FastAPI 允许开发者对路径操作函数中的参数声明额外的信息与校验规则,其底层依赖 Python 类型注解与 Pydantic 的字段校验能力。先看仓库中的最简起点示例 docs_src/query_params_str_validations/tutorial001_py310.py

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

这里 q 是唯一的查询参数,类型为 str | None,默认值为 None

关于该声明的两个关键结论:

  • = None 让参数可选:FastAPI 依据默认值判断参数是否必需。只要存在默认值 None,参数就不是必填项。
  • str | None 带来编辑器收益:联合类型可以让 IDE 与静态类型检查工具提供更好的补全与错误提示(例如避免在可能为 None 的值上直接调用字符串方法)。

但仅靠类型注解,我们尚无法表达"如果传了 q,它的长度不能超过 50 个字符"这类约束。这就需要引入 Query

二、引入 QueryAnnotated:声明式校验的基石

要实现额外校验,需要导入两个符号:

  • Query:来自 fastapi,是声明查询参数校验与元数据的核心工具;
  • Annotated:来自标准库 typing,用于把元数据"附加"到类型注解上。

对应代码见 docs_src/query_params_str_validations/tutorial002_an_py310.py

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

版本前提:FastAPI 从 0.95.0 版本起支持并推荐 Annotated 写法。若仓库版本过旧,使用 Annotated 会出现导入或类型解析错误,请先按 docs/en/docs/deployment/versions.md 中的升级指引将 FastAPI 升级到至少 0.95.1

2.1 把 Annotated 包在类型外层

在 Python 类型教程中我们已知 Annotated 可用于在类型上附加元数据(即 PEP 593 的 Type Hints with Metadata Annotations)。于是:

q: str | None = None

可以改写为:

q: Annotated[str | None] = None

两者语义完全相同——q 的类型仍是 str | None,默认值仍是 None。区别在于 Annotated 提供了"第二槽位"来放置 FastAPI 能读取的额外信息。

2.2 把 Query(max_length=50) 放进 Annotated

Query 放入 Annotated 的元数据槽位,并传入 max_length=50

q: Annotated[str | None, Query(max_length=50)] = None

注意这里函数参数的默认值仍是 None,所以 q 依旧可选;但 Query(max_length=50) 告诉 FastAPI:一旦该参数被提供,其值最多 50 个字符

关于 Query() 的适用范围:本教程用的是 Query(),因为它作用于查询参数。FastAPI 还有 Path()Body()Header()Cookie() 等同类工具,它们接受与 Query() 相同的参数集合,只是作用的参数来源不同,后续章节会逐一展开。

启用上述声明后,FastAPI 会自动完成三件事:

  1. 校验数据——确保长度不超过 50 字符;
  2. 给出清晰错误——当数据非法时向客户端返回结构化的 422 校验错误;
  3. 写入 OpenAPI 模式——参数会被记录进 OpenAPI 的 paths 定义中,从而自动出现在 /docs/redoc 交互式文档里。

三、旧式写法:把 Query 当作函数默认值(了解即可)

在 0.95.0 之前(即 2023 年 3 月前),FastAPI 要求把 Query() 直接放在函数参数的默认值位置,而不是放进 Annotated。老代码库中仍大量存在这种写法,官方文档用一节专门解释它,见 docs_src/query_params_str_validations/tutorial002_py310.py

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

由于无法使用 Annotated,就必须用 Query(default=None) 顶替原本的默认值 None

q: str | None = Query(default=None)

它与下面这句对于 FastAPI 而言等价(参数可选、默认 None):

q: str | None = None

差别在于前者显式声明了这是一个查询参数。在此基础上叠加 max_length 等字符串校验:

q: str | None = Query(default=None, max_length=50)

3.1 两种写法不可混用:Query 内部的 default

使用 Annotated 时,不能再给 Querydefault 参数,因为真正的默认值应该写在函数参数上,否则会产生歧义。例如下面这种写法不被允许

q: Annotated[str, Query(default="rick")] = "morty"

——因为 FastAPI 无法判断默认值到底是 "rick" 还是 "morty"。正确做法是二选一:

# 推荐:默认值写在函数参数上
q: Annotated[str, Query()] = "rick"

# 老式代码库常见
q: str = Query(default="rick")

3.2 为什么官方推荐 Annotated

官方在文档中明确指出:优先使用 Annotated,它在新代码中"有诸多好处、没有缺点",理由包括:

  • 默认值更符合 Python 直觉:函数参数上的默认值就是"真正的默认值",与其他普通 Python 函数一致;
  • 函数可脱离 FastAPI 复用:直接把同一函数当作普通函数调用即可。若某参数必填(无默认值),编辑器与 Python 解释器都会在漏传时立即报错;
  • 旧式写法有隐蔽陷阱:当函数在没有 FastAPI 的场景下被调用时,若忘记传入参数,拿到的会是 QueryInfo 之类的包装对象而不是真正的 str,且编辑器与解释器都不会提前报错,直到内部运算失败才暴露问题;
  • 元数据可叠加Annotated 支持多个元数据注解,同一签名还能被 Typer 等其他工具复用。

四、叠加更多校验:min_length 与正则 pattern

4.1 同时限制最小与最大长度

长度约束可以组合使用。示例见 docs_src/query_params_str_validations/tutorial003_an_py310.py

@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(min_length=3, max_length=50)] = None,
):
    ...

这样 q 一旦被传入,长度必须落在 [3, 50] 区间内。

4.2 用正则表达式限定取值形态

Query 还接受 pattern 参数,要求参数值必须匹配指定的正则表达式。示例见 docs_src/query_params_str_validations/tutorial004_an_py310.py

@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None, Query(min_length=3, max_length=50, pattern="^fixedquery$")
    ] = None,
):
    ...

"^fixedquery$" 为例,逐段解释其含义:

  • ^:锚定字符串开头,表示 fixedquery 之前不允许有任何字符;
  • fixedquery:要求中间这段必须恰好等于 fixedquery
  • $:锚定字符串结尾,表示 fixedquery 之后不允许再有字符。

正则表达式对不少开发者来说是个难点,不必焦虑——没有正则你也能完成大量工作,只需知道在 FastAPI 中需要时可以随时使用即可。

五、默认值与非 None 场景

默认值不限于 None。假设希望 q 的默认值是 "fixedquery",同时要求最小长度 3,可参考 docs_src/query_params_str_validations/tutorial005_an_py310.py

@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
    ...

注意此时类型注解已退化为单纯的 str(不再有 | None),因为默认值是具体字符串。

通用规则:只要参数带默认值(无论默认值是 None 还是其他类型),该参数就是**可选(非必填)**的。

六、必填查询参数的各种姿势

6.1 普通必填参数

若不需要额外校验或元数据,直接不给默认值即可:

q: str

而不是:

q: str | None = None

但如果还需要 Query,则同样只需省略默认值,见 docs_src/query_params_str_validations/tutorial006_an_py310.py

@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)]):
    ...

请求 URL 未携带 q 时,FastAPI 会返回 422 校验错误并说明该参数缺失。

6.2 "必填但可为 None"

还有一种边界情况:参数必须被客户端显式发送,但允许其值为 None。做法是保留 None 为合法类型、同时不写默认值,见 docs_src/query_params_str_validations/tutorial006c_an_py310.py

@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(min_length=3)]):
    ...

与 6.1 的区别仅在于类型是否包含 None:这里客户端必须发送 q 参数(即使发送 ?q= 这类空值),否则仍会判定为缺失。

七、多值查询参数:接收列表

7.1 同一参数名出现多次

只要显式使用 Query,就可以把参数声明为接收多个值。例如 URL 中 q 可以出现多次,见 docs_src/query_params_str_validations/tutorial011_an_py310.py

@app.get("/items/")
async def read_items(q: Annotated[list[str] | None, Query()] = None):
    query_items = {"q": q}
    return query_items

访问:

http://localhost:8000/items/?q=foo&q=bar

q 参数会以 Python list 形式进入函数,响应为:

{
  "q": [
    "foo",
    "bar"
  ]
}

为什么必须显式写 Query():若不写,list 类型的参数会被 FastAPI 误判为请求体的一部分,而非查询参数。

文档示意图展示了交互式文档中该参数会升级为"允许多个值输入"的控件:

FastAPI 交互式文档中多值查询参数界面截图

7.2 为多值列表提供默认值

未提供任何值时返回默认列表,见 docs_src/query_params_str_validations/tutorial012_an_py310.py

@app.get("/items/")
async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]):
    query_items = {"q": q}
    return query_items

直接访问 http://localhost:8000/items/(不带任何查询参数),q 取默认值,响应同样为:

{
  "q": [
    "foo",
    "bar"
  ]
}

7.3 直接使用裸 list 的代价

也可以只写 list 而不写 list[str],见 docs_src/query_params_str_validations/tutorial013_an_py310.py

@app.get("/items/")
async def read_items(q: Annotated[list, Query()] = []):
    ...

注意差异list[int] 会校验并文档化"列表内容必须是整数";而裸 list 不会校验元素类型。因此建议尽量给出元素类型,以获得完整校验与 OpenAPI 文档。

八、参数元数据:titledescriptionaliasdeprecated

下面的元数据声明都会进入生成的 OpenAPI 模式,被自动文档与外部代码生成等工具消费。需要说明的是,不同工具对 OpenAPI 的支持程度可能不同,个别字段未必全部展示,但这不影响其在模式中的存在。

8.1 titledescription

增加人类可读的标题,见 docs_src/query_params_str_validations/tutorial007_an_py310.py

@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(title="Query string", min_length=3)] = None,
):
    ...

再叠加长文本描述,见 docs_src/query_params_str_validations/tutorial008_an_py310.py

q: Annotated[
    str | None,
    Query(
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
    ),
] = None

8.2 alias:把参数映射到非法 Python 标识符

若希望 URL 中使用的参数名是 item-query(连字符使其不是合法 Python 变量名),最接近的变量名只能是 item_query。此时用 alias 声明"URL 中的真实名称",见 docs_src/query_params_str_validations/tutorial009_an_py310.py

@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(alias="item-query")] = None):
    ...

访问:

http://127.0.0.1:8000/items/?item-query=foobaritems

FastAPI 就会用别名 item-query 去 URL 中取值,并注入到参数 q

8.3 deprecated=True:标记参数已弃用

当某个参数仍需暂时保留以兼容存量客户端、但希望文档明确提示其"已弃用"时,见 docs_src/query_params_str_validations/tutorial010_an_py310.py

@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(alias="item-query")] = None,
    query: Annotated[
        str | None,
        Query(
            alias="item-query",
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
            max_length=50,
            pattern="^fixedquery$",
            deprecated=True,
        ),
    ] = None,
):
    ...

deprecated=True 的参数在文档界面中会被突出标记为弃用:

FastAPI 交互式文档中已弃用参数显示截图

8.4 include_in_schema=False:从 OpenAPI 中排除参数

若想让某个查询参数完全不出现在 OpenAPI 与自动文档中(例如仅供内部调试),设置 include_in_schema=False,见 docs_src/query_params_str_validations/tutorial014_an_py310.py

@app.get("/items/")
async def read_items(
    hidden_query: Annotated[str | None, Query(include_in_schema=False)] = None,
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}

该参数仍可正常接收与使用,只是不会出现在 /docs 等文档与导出的 OpenAPI 模式中。

九、Query 底层实现速览

从源码 fastapi/param_functions.py 可以确认 Query() 的完整参数体系,除本教程用到的之外还包括 gt/ge/lt/le(数字校验)、alias_priorityvalidation_aliasserialization_alias 等。结合定义与官方文档可得如下速查表:

类别 参数 作用 适用类型
基础 default 参数默认值(用 Annotated 时禁止使用) 任意
基础 default_factory 惰性生成默认值的可调用对象 任意
字符串 min_length 最小长度 str
字符串 max_length 最大长度 str
字符串 pattern 必须匹配的正则表达式 str
数字 gt/ge/lt/le 大于/大于等于/小于/小于等于 数值
元数据 title / description 供文档与外部工具展示的标题/描述 任意
元数据 alias 用于取值与 OpenAPI 的替代名称 任意
元数据 deprecated 在文档中标记为已弃用 任意
元数据 include_in_schema False 时从 OpenAPI 中排除 任意

实现层面,Query() 会构造一个 params.Query 描述对象,最终由 FastAPI 的依赖解析与 Pydantic 校验管道在请求到达时执行长度、正则与类型校验。类似的 Path()Body()Header()Cookie() 等函数定义也在同一文件(fastapi/param_functions.py)中,共享几乎一致的校验参数集——这也解释了为什么本章学到的 min_lengthpatternalias 等写法在后续章节可以直接迁移到路径参数、请求体字段等场景。

十、自定义校验:Pydantic AfterValidator

当内置校验参数不足以表达业务规则时,FastAPI 支持挂载自定义校验函数。它会在常规校验(例如"值确实是 str")之后执行,入口是 Pydantic v2 提供的 AfterValidator,同样放进 Annotated。Pydantic 还提供 BeforeValidator 等其他变体。

版本要求AfterValidator 需要 Pydantic 2 及以上版本。FastAPI 仓库默认基于 Pydantic v2(FastAPI 0.100.0+ 起),其 pydantic v1 兼容路径独立维护在 docs_src/pydantic_v1_in_v2 目录中。

10.1 完整示例:ISBN / IMDB 前缀校验

下面来自 docs_src/query_params_str_validations/tutorial015_an_py310.py 的例子演示了如何校验一个 item ID 必须以前缀 isbn-(图书 ISBN)或 imdb-(电影 IMDB 编号)开头:

import random
from typing import Annotated

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}

核心就一句话:把函数放进 AfterValidator(...),再连同 AfterValidator 一起放进 Annotated。函数签名接收待校验值,不满足时抛出 ValueError,满足则原样(或转换后)返回该值。若用户传了非法前缀,如 ?id=bogus-id,会收到校验错误;若不传 id,则会从 data 字典中随机返回一条记录作为演示建议。

10.2 逐行理解这段代码

如果只关心 FastAPI 的用法,可以跳过本小节;若想厘清代码细节:

  • str.startswith() 支持传入元组,会依次检查元组中的每个前缀(例如 ("isbn-", "imdb-"));
  • data.items() 返回一个由 (键, 值) 元组组成的可迭代对象,list(data.items()) 将其转成普通列表;
  • random.choice() 从列表中随机取出一个 (id, name) 元组,形如 ("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")
  • Python 的元组解包把这两个值分别赋给变量 idname,因此即便用户不提供 ID,也能得到一条随机的建议记录——所有这些只用了一行代码。

10.3 什么时候不该用自定义校验器

如果校验逻辑需要与外部组件(数据库、其他 API)通信,则应改用 FastAPI 依赖注入(Dependencies) 机制,而不是 AfterValidatorAfterValidator 只适合处理仅凭请求中已有数据即可完成的检查。

十一、小结:一张速查表

本章覆盖的校验与元数据可归为三类:

  • 通用元数据aliastitledescriptiondeprecated(另可加 include_in_schema);
  • 字符串专属校验min_lengthmax_lengthpattern
  • 自定义校验:借助 Pydantic AfterValidator 挂载自写校验函数。

这些能力都以 Query(...) 为入口、以 Annotated[类型, Query(...)] 为推荐载体。官方给出的泛化规则同样适用于后续章节中的 Path()Body()Header()Cookie()。本章所有示例代码位于仓库 docs_src/query_params_str_validations 目录,可直接运行验证;下一章将介绍如何为数字等非字符串类型声明类似校验(gtgeltle)。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388