FastAPI 依赖注入入门:用 `Depends` 构建共享逻辑与层级化依赖树
导读
FastAPI 内置了一套强大但直观的依赖注入(Dependency Injection)系统,它允许你的 path operation functions(路径操作函数)直接"声明"自己需要哪些东西(数据库连接、用户身份、通用查询参数……),剩下的一切由 FastAPI 在请求到达时自动完成:调用依赖、取得结果、把结果注入到函数参数。读完本篇,你将掌握定义依赖函数、通过 Depends 声明依赖、利用 Annotated 消除样板代码、混用 async def 与普通 def,以及理解"依赖可以再依赖其它依赖"从而构建层级化依赖树的全过程——这正是后续数据库、安全认证等所有集成功能的基石。
本文对应仓库中的入门章节 docs/es/docs/tutorial/dependencies/index.md(英文原版见 docs/en/docs/tutorial/dependencies/index.md),并结合仓库源码与示例代码做了源码级展开。
"依赖注入"到底指什么
依赖注入在编程中的含义是:你的代码(这里指 path operation functions)用一种方式声明自己运行所必需且会使用到的"依赖";随后由系统(这里指 FastAPI)负责完成其余工作,把依赖"注入"给你的代码。
也就是说,你不用自己去创建和获取那些东西,只要声明"我需要它",FastAPI 就会在恰当的时机调用依赖并传入结果。
它在真实项目里非常有用,典型场景包括:
- 共享逻辑:同一段代码逻辑反复出现,抽成依赖后只写一次;
- 共享数据库连接:多个接口复用同一个连接/会话;
- 实施安全约束:身份认证、鉴权、角色校验、付费用户限制等;
- 其它任意"在接口执行前必须准备好的东西"。
所有这一切都能在最大限度减少代码重复的前提下完成。
第一步:创建一个"可依赖"函数
依赖的本质非常简单:它就是一个普通的 Python 函数,并且可以像 path operation function 一样声明路径参数、查询参数、请求体、Header、Cookie 等各类参数。
先看本仓库中最基础的示例,完整代码见 docs_src/dependencies/tutorial001_an_py310.py:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
核心就是 common_parameters 这个函数——仅此而已,就这几行。它的外形和结构与普通的 path operation function 完全一致,可以把它理解成一个"没有装饰器(没有 @app.get(...))的路径操作函数"。
这个依赖声明了三个参数:
| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
q |
str | None |
None |
可选的查询参数 |
skip |
int |
0 |
分页起点 |
limit |
int |
100 |
每页数量上限 |
依赖函数可以返回任意类型——这里只是把三个参数原样打包成一个 dict 返回。
仓库中还保留了不使用 Annotated 的等价写法 docs_src/dependencies/tutorial001_py310.py,参数形式为 commons: dict = Depends(common_parameters),效果一致。
版本注意:
Annotated的支持(并成为推荐写法)始于 FastAPI 0.95.0。如果你使用更早的版本,使用Annotated会报错,请先将 FastAPI 升级到至少 0.95.1。
导入并声明 Depends
先从 fastapi 中导入 Depends:
from fastapi import Depends, FastAPI
然后在 path operation function 的新参数上使用它,用法和 Body、Query、Header 等一样:
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
虽然 Depends 在参数位置的书写方式与 Body、Query 相同,但工作机制不同:
- 它只接受一个参数:一个"可依赖的"(dependable)可调用对象,通常是函数;
- 不要手动调用它:不要写
Depends(common_parameters())这种带括号的形式,直接把函数对象传给Depends(); - 被传入的函数,其参数声明方式与 path operation function 完全相同。
当每一个新请求到达时,FastAPI 会自动完成三件事:
- 用正确的参数调用你的依赖函数;
- 拿到函数的返回值;
- 把该返回值赋值给 path operation function 中对应的参数。
于是代码中两个接口 /items/ 与 /users/ 共享了同一条分页逻辑,其调用关系可以用下图表示:
graph TB
common_parameters(["common_parameters"])
read_items["/items/"]
read_users["/users/"]
common_parameters --> read_items
common_parameters --> read_users
值得注意的是:你不需要创建任何特殊类,也不需要把它"注册"到 FastAPI 的某个地方。只需把它传给 Depends,FastAPI 自会处理好其余部分。
用 Annotated 共享同一依赖声明
上面的例子已经暴露出一小点代码重复:每次要用 common_parameters 依赖,都要完整写出带类型注解和 Depends() 的参数:
commons: Annotated[dict, Depends(common_parameters)]
由于使用了 Annotated,可以把这个 Annotated 值存入变量,再在多处复用。这正是仓库中 docs_src/dependencies/tutorial001_02_an_py310.py 演示的"别名(type alias)"技巧:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
CommonsDep = Annotated[dict, Depends(common_parameters)]
@app.get("/items/")
async def read_items(commons: CommonsDep):
return commons
@app.get("/users/")
async def read_users(commons: CommonsDep):
return commons
把 Annotated[dict, Depends(common_parameters)] 命名为 CommonsDep 后,两个接口都只写一行 commons: CommonsDep。
需要强调的是:这只是标准 Python 语法,并非 FastAPI 专属特性——它叫"类型别名"。正因为 FastAPI 建立在 Python 标准(包括 Annotated)之上,你才能直接使用这一技巧。依赖依旧按预期工作,并且最妙的是类型信息被完整保留:你的编辑器依然能提供自动补全、行内错误提示,mypy 等静态检查工具也能正常工作。
当代码库足够大、同一个依赖被几十上百个 path operation 反复使用时,这一技巧消除的样板代码量非常可观。
async def 还是普通 def?
依赖同样由 FastAPI 负责调用(与 path operation functions 一样),因此定义依赖时适用完全相同的规则:
- 你可以写
async def,也可以写普通def; async def的依赖可以出现在普通def的 path operation function 里;- 普通
def的依赖也可以出现在async def的 path operation function 里; - 混用没有关系——FastAPI 知道怎么处理(普通
def会被放入线程池执行,async def则在事件循环中执行)。
关于 async/await 基础概念,可参考仓库中的 Async 文档。
自动集成到 OpenAPI 与交互式文档
所有在依赖(及其子依赖)里声明的请求参数、校验规则与需求,都会被合并进同一个 OpenAPI schema。因此 /docs 交互式文档中会自动出现来自依赖的参数信息。
下面的截图来自仓库的 image01.png,展示的就是上例中由 common_parameters 声明的 q、skip、limit 三个查询参数如何出现在 Swagger UI 的 GET /items/ 接口面板里,包括参数类型、默认值以及 200/422 响应说明:
这意味着:当你在依赖中声明参数时,文档、校验、类型转换一整套能力都"免费"获得,不需要任何额外配置。
简单背后的思想:一切皆由框架编排
不妨退一步看:path operation functions 本身也不是你手动调用的——只要某个 path 与 operation 匹配,FastAPI 就会负责用正确的参数去调用它,并从请求中提取数据。实际上所有(或绝大多数)Web 框架都遵循这种模式。
依赖注入系统在此基础上更进一步:你可以告诉 FastAPI,"我的 path operation function 还依赖某个东西,它必须在我的函数执行前先运行",而 FastAPI 会负责执行它并把结果注入进来。
在其它技术社区中,同一概念常有不同叫法,本质都是一回事:
- resources(资源)
- providers(提供者)
- services(服务)
- injectables(可注入项)
- components(组件)
"插件"?不需要——依赖注入本身即是扩展机制
很多框架依靠"插件"机制来扩展能力,而 FastAPI 中并没有必要专门去造插件:通过依赖注入,你可以声明无穷无尽的集成与交互,并让它们对 path operation functions 可用。
具体做法通常是:直接 import 你需要的第三方 Python 包,再用几行代码把它集成进 API 函数——仅此而已。后续章节(关系型 / NoSQL 数据库、安全认证等)会给出大量实例。
这种简单性正是 FastAPI 生态兼容面广的原因,依赖注入系统可以与以下一切协同工作:
- 所有关系型数据库
- NoSQL 数据库
- 外部 Python 包
- 外部 API
- 认证与授权系统
- API 用量监控系统
- 响应数据注入系统
- 等等……
简单却强大:依赖可以层层嵌套
虽然这套层级化的依赖注入系统定义和使用都非常简单,它依然非常强大。依赖本身可以再声明依赖:你定义的依赖 A 依赖 B,B 又依赖 C……最终形成一棵层级化依赖树(hierarchical dependency tree),由依赖注入系统替你递归地求解所有依赖(及其子依赖),并在每一层把结果注入到位。
例如,假定 API 有 4 个端点:
/items/public//items/private//users/{user_id}/activate/items/pro/
仅凭依赖与子依赖的组合,就能为它们赋予互不相同的权限要求:
graph TB
current_user(["current_user"])
active_user(["active_user"])
admin_user(["admin_user"])
paying_user(["paying_user"])
public["/items/public/"]
private["/items/private/"]
activate_user["/users/{user_id}/activate"]
pro_items["/items/pro/"]
current_user --> active_user
active_user --> admin_user
active_user --> paying_user
current_user --> public
active_user --> private
admin_user --> activate_user
paying_user --> pro_items
对应关系一目了然:
/items/public/:只要求current_user(当前用户有效即可);/items/private/:要求active_user(激活用户),而active_user依赖current_user;/users/{user_id}/activate:要求admin_user(管理员),其先决条件是active_user;/items/pro/:要求paying_user(付费用户),同样先经过active_user。
所有这些依赖声明的参数、校验等需求,都会被 FastAPI 递归地加入每个 path operation,最终写进 OpenAPI schema,并在交互式文档中展示出来。
源码视角:Depends 与依赖求解是怎么实现的
理解背后的实现能让你的使用更加游刃有余。在 fastapi/params.py 中,Depends 是一个冻结数据类:
@dataclass(frozen=True)
class Depends:
dependency: Callable[..., Any] | None = None
use_cache: bool = True
scope: Literal["function", "request"] | None = None
三个字段分别对应:
dependency:被注入的"可依赖"可调用对象(Depends函数级文档见 fastapi/param_functions.py);use_cache:默认True。在同一请求中,若某个依赖被多处声明,第一次调用后的值会被缓存复用;设为False可禁用缓存、保证每次重新调用;scope:主要服务于含yield的依赖(详见 dependencies-with-yield),"function"表示依赖体包裹 path operation function 的执行区间,"request"表示包裹整个请求—响应周期。
有趣的旁证是:安全相关的 Security 类正是 Depends 的子类(见 fastapi/params.py),所以安全机制与依赖注入在底层是同一套体系。
从执行链路看,每个路径操作在注册时会被建模成 fastapi/dependencies/models.py 中的 Dependant 对象(见 fastapi/dependencies/models.py),其 dependencies 字段递归挂载子依赖,从而形成上文提到的依赖树;请求到达后,fastapi/dependencies/utils.py 的 solve_dependencies 异步函数(见 fastapi/dependencies/utils.py)负责自底向上解析整棵树,并把每个依赖的返回值注入到下一层。仓库 tests/ 目录下也提供了丰富的回归测试,例如 test_dependencies_utils.py、test_dependency_cache.py、test_dependency_overrides.py 与 test_dependency_class.py,分别覆盖依赖缓存、测试覆盖替换、类作为依赖等行为,可作为深入研读的入口。
后续进阶路线
本篇是依赖注入的"第一卷"。在本仓库的 dependencies 教程目录 下,还可以继续学习:
- 类作为依赖:除了函数,类也可以作为依赖使用(例如定义
CommonQueryParams类承载参数并注入实例); - 子依赖:深入嵌套依赖与"同一依赖多次使用"的细节;
- 路径操作装饰器中的依赖:声明只执行、不需要返回值的依赖;
- 含
yield的依赖:在依赖中管理资源生命周期与清理逻辑; - 全局依赖:为整个应用或路由组统一挂载依赖。
掌握本文中的 Depends 声明方式、Annotated 别名复用与层级依赖树思想后,再去看安全认证、数据库会话管理等进阶主题,你会发现它们全部建立在这一套统一、简洁的机制之上。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
