依赖注入 Depends 原理与生命周期

一句话总结

Depends 就是“我不关心依赖怎么造,你(FastAPI)帮我准备好塞进来”。它用函数参数声明依赖,框架在每次请求时自动解析、可嵌套、可缓存、可用 yield 做资源的‘开业+打烊’。结果:代码解耦、可测试、不重复。


生活类比:高级餐厅的后厨协作

把 API 想成餐厅。Depends 不是服务员,而是从点单到出餐的一整套协作流程

  • 预处理(前):后厨备料——校验参数、建数据库连接(yield 之前)

  • 出餐(中):把备好的料(db 会话、当前用户)交给厨师(路由函数)

  • 后处理(后):收盘、记日志、关连接(yield 之后)

FastAPI 为每个请求独立地解析依赖,互不串味,天然线程安全。

Depends 工作机制


flowchart TD

    REQ[请求到达] --> RESOLVE[FastAPI 解析依赖树]

    RESOLVE --> CALL[调用依赖函数, 解析其自身参数]

    CALL --> SUB[依赖还能依赖依赖 → 递归解析]

    SUB --> CACHE{同请求内已解析?}

    CACHE -->|是, use_cache=True| REUSE[复用同一结果]

    CACHE -->|否| COMPUTE[执行依赖, 缓存结果]

    COMPUTE --> INJECT[把结果注入路由参数]

    REJECT[路由函数执行]

    INJECT --> REJECT

    REUSE --> REJECT

    REJECT --> TEARDOWN[yield 依赖的收尾代码: 关连接/提交事务]

关键点

  • 依赖可以是任何 callable:普通函数、async def、类(含 __call__)、类构造器。

  • 子依赖会被递归解析,形成依赖链。

  • 默认 use_cache=True:同一请求内多次用到同一依赖,只执行一次(如两个路由都要 get_db,拿到的是同一个 session)。

三种依赖形态

形态写法典型用途
共享逻辑函数def common_params(q, skip, limit)分页、公共查询参数
资源型(yield)def get_db(): ... yield db ... db.close()数据库连接、事务
类依赖class Pagination: def __init__(self, page, size)分组参数、带状态

yield 依赖 = 生命周期管理(重点)

 
from typing import AsyncGenerator
 
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
 
 
 
async def get_db(session_factory: async_sessionmaker[AsyncSession]) \
 
        -> AsyncGenerator[AsyncSession, None]:
 
    async with session_factory() as session:   # 预处理: 开连接
 
        yield session                            # 把 session 交给路由
 
        # 后处理: 路由返回后这里才继续 (提交/回滚/关闭由 async with 负责)
 
 
 
@app.get("/users/{user_id}")
 
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
 
    ...
 

⚠️ 铁律yield 之后的代码在路由返回响应之后才执行(但响应发往客户端之前,默认 scope)。所以关闭连接、提交事务都放这里,但别在 yield 后写业务逻辑——那时请求早已处理完。

Depends(..., scope="request") 可让收尾延后到响应完全发送给客户端之后(适合写日志、 metrics)。

组合依赖:搭乐高式权限链

 
def get_token(authorization: str = Header(None)) -> str:
 
    if not authorization or not authorization.startswith("Bearer "):
 
        raise HTTPException(401, "缺少 Authorization")
 
    return authorization.removeprefix("Bearer ")
 
 
 
def get_current_user(token: str = Depends(get_token)) -> dict:
 
    # 解码 JWT, 取用户
 
    ...
 
 
 
def get_admin_user(user: dict = Depends(get_current_user)) -> dict:
 
    if "admin" not in user.get("roles", []):
 
        raise HTTPException(403, "权限不足")
 
    return user
 
 
 
@app.get("/admin/dashboard")
 
async def dashboard(admin: dict = Depends(get_admin_user)):
 
    ...
 

一层套一层:get_token → get_current_user → get_admin_user。每层只管自己那点事,测试时可单独 mock 某一层。

与 Flask/Spring 对比

框架注入方式特点
FastAPI方法参数注入 Depends()轻量、Pythonic、依赖即函数
Spring@Autowired 构造器注入重、IoC 容器管理生命周期
Flask通常手动 g.xxx 或上下文无原生 DI,靠 flask.g

易错点

  1. 普通 def 依赖不能依赖 async def 依赖:同步函数里不能 await,会报错。反过来可以。

  2. 循环依赖:A 依赖 B,B 又依赖 A → FastAPI 直接报错。设计上要避免。

  3. use_cache=False:需要每次都重新执行时用,比如每次都要新鲜时间戳的依赖。

  4. app.dependency_overrides:测试时把真实依赖换成假依赖(mock),是 FastAPI 可测试性的核心武器。

记忆口诀

Depends 不是调用,是声明——框架替你调用并注入。

yield 前备料,yield 后收摊,中间交给路由。

同请求同依赖只算一次(缓存),要新鲜就 use_cache=False。

依赖能套依赖,搭出权限乐高链。


▶ 对应实操:02-路径参数与查询参数

▶ 对应实操:06-依赖注入

速记卡(面试闪卡)

Q1:一句话讲清「依赖注入 Depends 原理与生命周期」到底是什么?

A:Depends 是声明而非调用:框架按参数自动解析依赖、可嵌套可缓存、用 yield 管生命周期。

Q2:工作机制像餐厅后厨 —— 怎么理解?

A:把 API 想成餐厅:Depends 是”从点单到出餐的协作流程”。前处理(yield 前)备料——校验参数、开连接;出餐(中)把 db 会话/当前用户交给路由;后处理(yield 后)收盘关连接。FastAPI 每请求独立解析依赖,互不串味、天然线程安全。

Q3:三种依赖形态 —— 怎么理解?

A:① 共享逻辑函数抽公共参数(分页);② 资源型 yield——管连接/事务生命周期;③ 类依赖把一组参数封装成对象按类型注解解析。组合依赖像搭乐高:get_token→get_current_user→get_admin_user 一层套一层,每层只管自己那点事,测试可单独 mock 某层。

Q4:yield 依赖的生命周期铁律 —— 怎么理解?

A:yield 前准备资源、yield 把值交路由、yield 后(路由返回后)才收尾关连接/提交事务。铁律:别在 yield 后写业务逻辑——那时请求早处理完。要收尾延到响应彻底发给客户端(写日志/metrics),用 Depends(…, scope=“request”)。对比:FastAPI 参数注入轻量、Spring @Autowired 靠 IoC 容器、Flask 靠 flask.g 凑。

Q5:易错点 —— 怎么理解?

A:四个坑:① 普通 def 依赖不能依赖 async def(同步里不能 await 报错,反过来可以);② 循环依赖 A↔B FastAPI 直接报错;③ use_cache=False 需每次重新执行(如新鲜时间戳);④ app.dependency_overrides 测试时换假依赖(mock),是 FastAPI 可测试性核心武器。记住”Depends 是声明不是调用”。

Q6:核心速记主线有哪些?

  • Depends=声明而非调用,框架解析并注入

  • 递归解析依赖树+同请求内 use_cache 只算一次

  • 三形态:共享函数/资源 yield/类依赖

  • yield 前备料后收摊,scope=request 延后收尾

  • 组合依赖搭权限链;易错点(循环依赖、dependency_overrides)

口诀

A:Depends 是声明,框架替你装

依赖递归树,同请求只一桩

yield 前备料后收摊,路由中间把活扛

层层套出权限链,测试 mock 不慌张

相关链接

相关链接