FastAPI: 依赖注入系统 — FastAPI 的灵魂设计

最后更新:2026-08-26

依赖注入就像乐高积木的接口——每块积木只关心自己该提供什么,不需要知道谁在用它。DB 连接、当前用户、权限检查,层层嵌套自动解析。

1. 你将学到


2. Alice 的真实故事

(1) 痛点:每个端点重复获取 DB 和用户

Alice 的 PriceTracker 有 20 个端点,每个都需要打开数据库连接、验证 JWT Token、获取当前用户、检查权限。她在每个端点函数里复制粘贴同样的 15 行初始化代码,一旦 DB 连接方式变化,需要修改 20 个地方。

(2) 依赖注入的解法

FastAPI 的依赖注入将重复逻辑提取为可复用的依赖函数,端点只需声明 db = Depends(get_db),FastAPI 自动解析和注入,包括子依赖的递归解析。

PYTHON
from fastapi import Depends

async def get_db():
    db = Database()
    yield db
    db.close()

async def get_current_user(token: str, db=Depends(get_db)):
    return verify_token(token, db)

@app.get("/products")
async def list_products(user=Depends(get_current_user), db=Depends(get_db)):
    # user and db are auto-injected
    ...

(3) 收益

DB 连接、用户验证、权限检查从 20 个端点各写 15 行变成 3 个依赖函数,修改一处全局生效。测试时只需 app.dependency_overrides[get_db] = lambda: mock_db,一行替换所有端点的数据库。


3. Depends() 基础

(1) 函数依赖

▶ 示例:简单函数依赖

PYTHON
from fastapi import FastAPI, Depends

app = FastAPI()

def common_parameters(
    q: str | None = None,
    skip: int = 0,
    limit: int = 100,
):
    return {"q": q, "skip": skip, "limit": limit}

@app.get("/products")
async def list_products(commons: dict = Depends(common_parameters)):
    return commons

@app.get("/prices")
async def list_prices(commons: dict = Depends(common_parameters)):
    return commons

输出:

TEXT 📖 仅展示
# 函数定义成功

▶ 示例:类依赖

PYTHON
from fastapi import FastAPI, Depends, Query

class CommonQueryParams:
    def __init__(
        self,
        q: str | None = Query(None),
        skip: int = Query(0, ge=0),
        limit: int = Query(100, ge=1, le=200),
    ):
        self.q = q
        self.skip = skip
        self.limit = limit

app = FastAPI()

@app.get("/products")
async def list_products(commons: CommonQueryParams = Depends(CommonQueryParams)):
    return {"q": commons.q, "skip": commons.skip, "limit": commons.limit}

输出:

TEXT 📖 仅展示
# 函数定义成功

(2) 函数依赖 vs 类依赖

维度 函数依赖 类依赖
定义方式 def get_xxx() class Xxx: + __init__
适用场景 简单逻辑、单次调用 需要状态、多方法
参数解析 函数参数自动解析 __init__ 参数自动解析
复用性
推荐度 首选 复杂场景使用

4. 子依赖嵌套

(1) 依赖解析树

100%
graph TD
    A[get_current_user] --> B[get_db]
    A --> C[get_token_from_header]
    C --> D[OAuth2PasswordBearer]
    
    E[require_admin] --> A
    E --> F[check_subscription]
    
    style A fill:#e1f5fe
    style E fill:#fff3e0

FastAPI 递归解析依赖树:require_adminget_current_userget_db,每个依赖只实例化一次(同一请求内缓存)。

▶ 示例:子依赖嵌套——DB → User

PYTHON
from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

# Level 1: Database session
async def get_db():
    db = {"connection": "active"}
    try:
        yield db
    finally:
        db["connection"] = "closed"

# Level 2: Get current user (depends on DB)
async def get_current_user(
    authorization: str = Header(...),
    db: dict = Depends(get_db),
):
    if not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Invalid token")
    token = authorization.replace("Bearer ", "")
    # Simplified: in production, verify JWT
    user = {"id": 1, "username": "alice", "role": "admin"}
    return user

# Level 3: Require admin (depends on current user)
async def require_admin(user: dict = Depends(get_current_user)):
    if user["role"] != "admin":
        raise HTTPException(status_code=403, detail="Admin required")
    return user

@app.get("/admin/stats")
async def admin_stats(admin: dict = Depends(require_admin)):
    return {"admin": admin["username"], "total_products": 1000000}

输出:

TEXT 📖 仅展示
# 函数定义成功

5. 依赖生命周期与 yield

(1) 请求级 vs 应用级

生命周期 声明方式 作用域 典型用途
请求级 def dep(): yield x; cleanup 每次请求创建+销毁 DB Session、Redis 连接
应用级 @app.on_event("startup") 应用启动时创建 引擎初始化、连接池

▶ 示例:yield 依赖与资源清理

PYTHON
from fastapi import FastAPI, Depends

app = FastAPI()

# Request-scoped dependency with cleanup
async def get_db_session():
    # Setup: create session
    session = {"id": "session-123", "active": True}
    print(f"DB session opened: {session['id']}")
    try:
        yield session  # This is injected into the endpoint
    finally:
        # Cleanup: close session (runs after response)
        session["active"] = False
        print(f"DB session closed: {session['id']}")

@app.get("/products")
async def list_products(db: dict = Depends(get_db_session)):
    print(f"Using DB session: {db['id']}")
    return {"session_id": db["id"]}

输出(服务端日志):

TEXT 📖 仅展示
DB session opened: session-123
Using DB session: session-123
DB session closed: session-123

6. 全局依赖与路由组依赖

(1) 不同范围的依赖声明

▶ 示例:路由组依赖

PYTHON
from fastapi import FastAPI, Depends, APIRouter, Header, HTTPException

async def verify_api_key(x_api_key: str = Header(...)):
    if x_api_key != "secret-key-123":
        raise HTTPException(status_code=401, detail="Invalid API key")
    return x_api_key

app = FastAPI()

# Public routes - no auth required
public_router = APIRouter()

@public_router.get("/health")
async def health():
    return {"status": "healthy"}

# Protected routes - API key required for all routes in this group
protected_router = APIRouter(dependencies=[Depends(verify_api_key)])

@protected_router.get("/products")
async def list_products():
    return [{"id": 1, "name": "Widget"}]

@protected_router.post("/products")
async def create_product():
    return {"id": 2, "name": "New Product"}

# Register routers
app.include_router(public_router)
app.include_router(protected_router, prefix="/api/v1")

输出:

TEXT 📖 仅展示
# 函数定义成功
依赖范围 声明位置 影响范围
端点级 @app.get("/", dependencies=[...]) 单个端点
路由组级 APIRouter(dependencies=[...]) 路由组所有端点
全局级 FastAPI(dependencies=[...]) 应用所有端点

7. 依赖覆盖与测试

(1) dependency_overrides

测试时需要替换真实依赖(如数据库连接、外部 API),app.dependency_overrides 允许在不修改源码的情况下替换依赖实现。

▶ 示例:测试中替换数据库依赖

PYTHON
from fastapi.testclient import TestClient
from fastapi import FastAPI, Depends

app = FastAPI()

# Real dependency
async def get_db():
    return {"type": "postgresql", "host": "prod-db"}

# Mock dependency for testing
def get_mock_db():
    return {"type": "sqlite", "host": "memory"}

@app.get("/db-info")
async def db_info(db: dict = Depends(get_db)):
    return db

# In tests:
def test_db_info():
    app.dependency_overrides[get_db] = get_mock_db
    client = TestClient(app)
    response = client.get("/db-info")
    assert response.json() == {"type": "sqlite", "host": "memory"}
    # Clean up
    app.dependency_overrides.clear()

输出:

TEXT 📖 仅展示
# 函数定义成功

❓ 常见问题

Q Depends 的依赖会被多次调用吗?
A 同一请求内,同一个依赖只执行一次,结果被缓存复用。不同请求会重新执行。
Q yield 依赖的 cleanup 什么时候执行?
A 在响应发送给客户端之后执行。如果端点抛出异常,cleanup 仍然执行(类似 try/finally)。
Q 依赖能嵌套多深?
A 没有硬性限制,但超过 3 层嵌套会增加调试难度。PriceTracker 的三层 DI 链(get_db → get_current_user → require_admin)是推荐的上限。
Q 同步和异步依赖能混用吗?
A 可以。FastAPI 自动处理同步/异步依赖。同步依赖在线程池中执行,异步依赖在事件循环中执行。
Q dependency_overrides 会影响其他测试吗?
A 会,因为是修改 app 对象。务必在测试结束后调用 app.dependency_overrides.clear() 或使用 fixture 管理。
Q 类依赖的 init 参数怎么注入?
A 和函数依赖一样,init

📖 小节

  • Depends() 将重复逻辑提取为可复用的依赖函数或类,端点只需声明不需要关心实现
  • 子依赖自动递归解析,同请求内缓存,避免重复执行
  • yield 依赖实现请求级生命周期管理,确保资源创建和清理配对
  • 路由组依赖 (APIRouter(dependencies=[...])) 让一组端点共享认证/校验逻辑
  • app.dependency_overrides 是测试利器,一行代码替换所有端点的真实依赖

📝 作业

  1. 基础题(难度⭐):创建 get_db 依赖函数,返回一个字典模拟数据库连接,在两个端点中注入并使用。提示:db: dict = Depends(get_db)
  2. 进阶题(难度⭐⭐):实现两层嵌套依赖:get_dbget_current_user(从请求头读 Authorization),在 /me 端点注入当前用户信息。提示:user: dict = Depends(get_current_user)
  3. 挑战题(难度⭐⭐⭐):实现 PriceTracker 三层 DI 链:get_db(yield + cleanup)→ get_current_user(验证 token)→ require_subscription("pro")(检查订阅级别),并用 dependency_overrides 测试时替换 DB 依赖。提示:yield 依赖 + app.dependency_overrides[get_db] = mock_fn

---|

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏