03-依赖注入
Python 3.11+
本章讲解 FastAPI 强大的依赖注入系统。
第一部分:依赖注入基础
1.1 实际场景
多个路由需要访问数据库,不想在每个路由中重复创建数据库连接。
问题:如何共享通用逻辑和资源?
1.2 什么是依赖注入?
依赖注入是一种设计模式,允许将依赖项从外部注入到函数或类中。
python
from fastapi import FastAPI, Depends
app: FastAPI = FastAPI()
# 定义依赖
def get_database() -> dict[str, str]:
return {"connection": "database"}
# 使用依赖
@app.get("/items")
def get_items(db: dict[str, str] = Depends(get_database)) -> dict[str, dict]:
return {"db": db}第二部分:函数依赖
2.1 实际场景
分页参数在多个接口中都需要使用,每个接口都需要 skip 和 limit 参数。
问题:如何复用通用参数逻辑?
2.2 基本依赖函数
python
from fastapi import FastAPI, Depends
app: FastAPI = FastAPI()
# 简单依赖
def get_query_param() -> str:
return "default"
@app.get("/items")
def read_items(q: str = Depends(get_query_param)) -> dict[str, str]:
return {"query": q}
# 带参数的依赖
def get_pagination(skip: int = 0, limit: int = 10) -> dict[str, int]:
return {"skip": skip, "limit": limit}
@app.get("/items")
def get_items(pagination: dict[str, int] = Depends(get_pagination)) -> dict[str, int]:
return pagination2.3 依赖链
python
def get_database() -> dict[str, str]:
return {"db": "connected"}
def get_current_user(db: dict[str, str] = Depends(get_database)) -> dict[str, str | int]:
return {"username": "john", "id": 1}
@app.get("/users/me")
def read_current_user(user: dict[str, str | int] = Depends(get_current_user)) -> dict[str, str | int]:
return user第三部分:类依赖
3.1 实际场景
分页逻辑不仅有参数,还有方法如计算偏移量,适合封装成类。
问题:如何将类作为依赖?
3.2 类作为依赖
python
from fastapi import FastAPI, Depends
app: FastAPI = FastAPI()
class Pagination:
def __init__(self, skip: int = 0, limit: int = 10):
self.skip: int = skip
self.limit: int = limit
@app.get("/items")
def get_items(pagination: Pagination = Depends()) -> dict[str, int]:
return {"skip": pagination.skip, "limit": pagination.limit}3.3 带方法的类
python
class Database:
def __init__(self) -> None:
self.connection: str = "connected"
def query(self, sql: str) -> list[dict[str, str]]:
return [{"id": 1, "name": "Item 1"}]
def close(self) -> None:
self.connection = "disconnected"
def get_database():
db: Database = Database()
try:
yield db
finally:
db.close()
@app.get("/items")
def get_items(db: Database = Depends(get_database)) -> list[dict[str, str]]:
return db.query("SELECT * FROM items")第四部分:依赖参数
4.1 实际场景
依赖需要使用路由的路径参数或查询参数。
问题:如何在依赖中访问请求参数?
4.2 路径参数
python
def get_item_id(item_id: int) -> int:
return item_id
@app.get("/items/{item_id}")
def read_item(item_id: int = Depends(get_item_id)) -> dict[str, int]:
return {"item_id": item_id}4.3 查询参数
python
def get_search_query(q: str = "default") -> str:
return q
@app.get("/search")
def search(q: str = Depends(get_search_query)) -> dict[str, str]:
return {"query": q}第五部分:可选依赖
5.1 实际场景
某些接口既支持公开访问,也支持认证访问,认证后返回更多信息。
问题:如何定义可选的依赖?
5.2 可选依赖
python
from typing import Any
def get_optional_db() -> str:
return "db"
@app.get("/items")
def get_items(db: str | None = Depends(get_optional_db, use_cache=False)) -> dict[str, str | None]:
return {"db": db}第六部分:完整示例
python
from fastapi import FastAPI, Depends, Header
from typing import Any
app: FastAPI = FastAPI()
# ==================== 依赖定义 ====================
class Pagination:
def __init__(self, skip: int = 0, limit: int = 10):
self.skip: int = skip
self.limit: int = limit
def get_pagination(skip: int = 0, limit: int = 10) -> Pagination:
return Pagination(skip=skip, limit=limit)
def verify_token(authorization: str | None = Header(default=None)) -> dict[str, str | int] | None:
if not authorization:
return None
if not authorization.startswith("Bearer "):
return None
token: str = authorization.replace("Bearer ", "")
return {"token": token, "user_id": 1}
def get_current_user(token_data: dict[str, str | int] | None = Depends(verify_token)) -> dict[str, str | int]:
if not token_data:
from fastapi import HTTPException
raise HTTPException(status_code=401, detail="Not authenticated")
return {"id": token_data["user_id"], "username": "john"}
# ==================== 路由 ====================
@app.get("/public/items")
def get_public_items(pagination: Pagination = Depends(get_pagination)) -> list[dict[str, str]]:
items: list[dict[str, str]] = [{"id": i, "name": f"Item {i}"} for i in range(1, 101)]
return items[pagination.skip:pagination.skip + pagination.limit]
@app.get("/private/items")
def get_private_items(
pagination: Pagination = Depends(get_pagination),
current_user: dict[str, str | int] = Depends(get_current_user)
) -> dict[str, str | list]:
items: list[dict[str, str]] = [{"id": i, "name": f"Private Item {i}"} for i in range(1, 51)]
return {
"user": current_user["username"],
"items": items[pagination.skip:pagination.skip + pagination.limit]
}
@app.get("/profile")
def get_profile(current_user: dict[str, str | int] = Depends(get_current_user)) -> dict[str, str | int]:
return current_user第七部分:L3 专家层
7.1 FastAPI 依赖注入的图解析(解决依赖链)
FastAPI 的依赖注入系统本质上是一个有向无环图(DAG)解析器。当路由函数声明了依赖,FastAPI 会构建依赖图并按拓扑序执行。
图解析流程:
路由函数: get_user(user=Depends(get_current_user))
│
▼
┌───────────────┐
│ get_current_ │
│ user │
│ Depends: │
│ get_db │
└───────┬───────┘
│
▼
┌───────────────┐
│ get_db │
│ (叶子节点) │
└───────────────┘
解析后的执行顺序(拓扑排序):
Step 1: get_db() ← 无依赖,最先执行
│
▼
Step 2: get_current_user(db=result_of_step1)
│
▼
Step 3: get_user(user=result_of_step2)
│
▼
返回响应核心实现(fastapi.dependencies.utils.solve_dependencies):
python
# 伪代码展示图解析逻辑
async def solve_dependencies(
*,
request: Request,
dependant: Dependant, # 包含依赖函数的描述
cache_dependencies: dict[int, Any], # 依赖缓存
) -> dict[str, Any]:
values: dict[str, Any] = {}
sub_dependant: Dependant
for sub_dependant in dependant.dependencies:
# 检查缓存
cache_key = id(sub_dependant.call)
if cache_key in cache_dependencies:
values[sub_dependant.name] = cache_dependencies[cache_key]
continue
# 递归解析子依赖
solved = await solve_dependencies(
request=request,
dependant=sub_dependant,
cache_dependencies=cache_dependencies,
)
# 调用依赖函数
result = await run_in_threadpool(sub_dependant.call, **solved)
values[sub_dependant.name] = result
return values依赖图的特点:
- DAG 结构:不允许循环依赖,FastAPI 在启动时检测并报错
- 拓扑排序:按依赖关系确定执行顺序,无依赖的先执行
- 重复依赖合并:同一个依赖函数在图中出现多次时只执行一次(除非
use_cache=False)
7.2 yield 依赖的生命周期管理
使用 yield 的依赖(如数据库会话)由 FastAPI 的生命周期管理机制处理。
生命周期流程:
请求开始
│
▼
┌──────────────────────────────────────┐
│ 1. 执行 yield 之前的代码(建立资源) │
│ db = SessionLocal() │
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 2. yield 资源给路由函数 │
│ yield db │
│ 路由函数执行... │
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 3. 路由函数完成后 │
│ a) 正常 → 执行 yield 之后的代码 │
│ try: yield db │
│ finally: db.close() ← 执行 │
│ b) 异常 → 先传播异常再执行清理 │
│ finally 块仍会执行 │
└──────────────────────────────────────┘
│
▼
响应返回给客户端底层实现:
- FastAPI 使用
contextlib.contextmanager或contextlib.asynccontextmanager包装 yield 依赖 - 依赖被包装为
Dependant对象,其中solve方法处理整个生命周期 - 异常传播:如果路由抛出异常,FastAPI 会在
finally块中确保资源被清理,然后让异常继续传播
python
# 内部处理逻辑(简化)
async def solve_yield_dependant(call: Callable) -> AsyncGenerator:
cm = contextlib.asynccontextmanager(call)() # 创建异步上下文管理器
try:
value = await cm.__aenter__() # 执行 yield 前代码
yield value # yield 给路由
except Exception as e:
await cm.__aexit__(type(e), e, e.__traceback__) # 传递异常信息
raise
else:
await cm.__aexit__(None, None, None) # 正常退出7.3 依赖缓存机制
FastAPI 默认对依赖结果进行缓存,同一请求内的相同依赖只执行一次。
缓存行为:
请求: GET /users/1/items/2
依赖声明:
/users/{user_id} → Depends(get_user)
/items/{item_id} → Depends(get_item)
路由函数 → Depends(get_current_user)
└─ Depends(get_user) ← 相同依赖
执行:
1. get_user(user_id=1) ← 执行,结果缓存
2. get_item(item_id=2) ← 执行,结果缓存
3. get_current_user()
└─ get_user(user_id=1) ← 命中缓存,跳过执行
缓存键: id(dependant.call) # 依赖函数的内存地址缓存控制:
Depends(func, use_cache=True):默认值,启用缓存Depends(func, use_cache=False):每次调用都执行,适用于需要获取最新状态的场景
缓存的作用域: 缓存仅在当前请求内有效,不同请求之间不共享缓存结果。
7.4 性能考量
| 维度 | 说明 | 建议 |
|---|---|---|
| 图解析 | 启动时构建依赖图,运行时复用 | 依赖深度对性能影响极小 |
| 依赖缓存 | 同一请求内默认缓存 | 减少重复的 DB 查询或 API 调用 |
| yield 依赖 | 上下文管理器有微小开销 | 资源管理类依赖应使用 yield |
| 同步依赖 | 放入线程池执行 | I/O 密集型用 async def 依赖 |
| 依赖链深度 | 拓扑排序时间复杂度 O(V+E) | 实际场景中几乎无影响 |
7.5 设计动机
| 设计选择 | 原因 |
|---|---|
| 图解析而非简单栈 | 支持复杂的依赖关系,自动解决执行顺序 |
| 默认缓存 | 大多数场景下同一依赖在同一请求中只需执行一次 |
| yield 支持 | 资源管理的 Pythonic 方式(RAII 模式) |
| 类型注解驱动 | 利用类型系统进行依赖推断和文档生成 |
7.6 知识关联
依赖注入
│
┌────────────────┼────────────────┐
▼ ▼ ▼
图解析算法 生命周期管理 缓存机制
(DAG 拓扑排序) (yield/finally) (per-request)
│ │ │
▼ ▼ ▼
Dependant 对象 上下文管理器 use_cache 参数
solve_deps() asynccontextmgr 缓存键 = id(func)
│ │ │
└────────────────┼────────────────┘
▼
01-FastAPI 入门
04-数据库集成 (DB Session)总结
| 知识点 | 说明 |
|---|---|
| Depends | 依赖注入 |
| 函数依赖 | 简单依赖函数 |
| 类依赖 | 类作为依赖 |
| 依赖链 | 依赖嵌套 |
| 可选依赖 | Optional |