在学习和使用 Python Web框架,特别是FastAPI时,你很可能听说过“依赖注入”这个听起来有些高大上的概念。初次接触时,很多人会觉得它很复杂、很“牛”。其实,依赖注入并没有想象中那么难,它是一种能显著提升代码质量、让逻辑变得更清晰的实用编程模式。
今天,我们就来快速搞懂FastAPI中的依赖注入,并学会三个立即可用的核心技巧。
依赖注入是什么?一个生动的比喻
想象一下你点外卖的过程:
- 传统写法 = 你自己去菜市场买菜、回来洗菜、切菜、开火炒菜、最后装盘。
- 依赖注入 = 你打开外卖APP,告诉它:“我要一份宫保鸡丁”。稍后,外卖员就会把做好的菜送到你手上。
看出区别了吗?
- 传统写法:函数内部自己创建所有需要的东西(比如数据库连接、配置文件、工具类实例)。
- 依赖注入:函数只需声明“我需要什么”,框架(或调用者)会自动把准备好的东西“注入”进来。
这就是依赖注入的核心思想:声明依赖,而非创建依赖。它让函数专注于自己的核心业务逻辑,而将获取外部资源的职责交给外部管理。

上代码:传统写法 vs 依赖注入写法
假设我们需要开发一个获取当前用户信息的接口 /me。它需要完成:1. 验证请求头中的Token并解析出用户ID;2. 连接数据库;3. 查询用户是否存在。
❌ 传统写法:臃肿且重复
在传统写法中,所有逻辑都堆积在路由处理函数里:
from fastapi import FastAPI, Header, HTTPException
import jwt
app = FastAPI()
@app.get("/me")
def get_current_user(authorization: str = Header(None)):
# 1. 解析token
# 进行用户判断是否有权限
# 2. 连接数据库
db = create_db_connection() # 自己创建连接
user = db.query(User).filter(User.id == user_id).first()
db.close() # 还要自己关闭数据库连接
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return user
这种写法的问题很明显:
- 代码重复:每个需要认证或数据库操作的接口,都要重复写Token解析和数据库连接代码。
- 资源泄露风险:容易忘记关闭数据库连接,占用连接池资源。
- 耦合度高,难以维护:业务逻辑与资源获取逻辑混杂,修改一处可能牵动多处。
✅ 依赖注入写法:清晰且优雅
现在,我们使用FastAPI的 Depends 来改造上面的代码:
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
app = FastAPI()
security = HTTPBearer()
# 1. 把token解析抽成依赖函数
async def get_current_user_id(
credentials: HTTPAuthorizationCredentials = Depends(security)):
# 这里实现具体的Token验证与解析逻辑,返回用户ID
# 权限处理
return user_id
# 2. 把数据库连接管理抽成依赖函数
async def get_db():
db = create_db_connection() # 处理连接和关闭数据库连接
try:
yield db # 将db对象提供给使用方
finally:
db.close() # 使用完毕后自动关闭
# 3. 路由函数只关注核心业务逻辑
@app.get("/me")
async def get_me(
user_id: int = Depends(get_current_user_id), # 注入用户ID
db = Depends(get_db)): # 注入数据库会话
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return user
这里的关键是 Depends。它告诉FastAPI:在执行 get_me 函数之前,请先执行 get_current_user_id 和 get_db 这两个函数,并把它们的结果(user_id 和 db)传给我。
依赖注入带来的好处:
- 代码高度复用:
get_current_user_id 和 get_db 可以在所有需要的地方通过 Depends() 调用。
- 资源自动管理:
get_db 函数利用 yield 确保了数据库连接在使用后总能被正确关闭,即使接口处理过程中发生了异常。
- 关注点分离:路由函数变得非常清爽,一眼就能看出它要做什么,可读性大幅提升。
- 便于测试:依赖可以被轻松替换(Mock),单元测试更容易编写。
依赖注入的三大核心实战技巧
基于上面的例子,我们可以总结出三个让代码变优雅的“酷炫技能”。
技巧一:重复代码抽成依赖
这是依赖注入最直接的价值。将认证、数据库连接、获取通用配置等重复性代码抽象成独立的依赖函数。
# 定义一次
async def get_current_user_id(
credentials: HTTPAuthorizationCredentials = Depends(security)):
...
# 在无数个接口中复用
@app.get("/posts")
async def get_posts(user_id: int = Depends(get_current_user_id)):
...
@app.post("/posts")
async def create_post(user_id: int = Depends(get_current_user_id)):
...
@app.delete("/posts/{id}")
async def delete_post(user_id: int = Depends(get_current_user_id)):
...
技巧二:资源管理用 yield
对于数据库连接、文件句柄、网络连接等需要妥善管理生命周期(打开/关闭)的资源,使用带有 yield 的依赖函数是完美选择。
async def get_db():
db = create_db_connection()
try:
yield db # 请求处理期间使用这个db
finally:
db.close() # 请求结束后,无论成功与否,都会执行关闭
yield 的工作机制:
- 在请求开始时,执行
yield 之前的代码,获取资源(连接数据库)。
- 将
yield 产生的值(db)注入给路由函数使用。
- 在路由函数执行完毕后(或抛出异常后),回来执行
finally 块中的代码,安全释放资源。
这保证了资源永不泄露,是编写健壮后端服务的必备模式。
技巧三:测试时直接传 Mock
由于路由函数通过参数声明依赖,而不是在内部硬编码创建,因此在单元测试中,我们可以直接传入模拟(Mock)对象,无需改动生产代码。
# 测试代码示例
async def test_get_me():
mock_db = MagicMock() # 创建一个模拟的数据库对象
# 设置模拟对象的行为
mock_db.query.return_value.filter.return_value.first.return_value = {"id": 1, "name": "张三"}
# 直接调用路由函数,并传入模拟的依赖
result = await get_me(user_id=1, db=mock_db)
assert result["name"] == "张三"
这种方式让单元测试变得极其简单和高效。

常见误区与避坑指南
- ❌ 误区一:过度使用依赖注入。不是所有东西都需要注入,简单的工具函数直接调用即可。过度设计会增加不必要的复杂度。
- ❌ 误区二:依赖链过长。尽量避免依赖嵌套超过3层(如A依赖B,B依赖C,C依赖D)。过深的依赖链会降低代码可读性和可调试性,应考虑重构。
- ❌ 误区三:在依赖函数中编写业务逻辑。依赖函数的职责应该限定在“提供资源或数据”上(如获取当前用户、提供数据库会话)。具体的业务计算和流程控制,应该放在路由函数或专门的业务层中。
总结
依赖注入不是一种用来炫技的复杂模式,而是一种切实提升代码可维护性、可测试性和优雅度的工程设计方法。在FastAPI中,它通过 Depends 变得异常简单和强大。
记住这三个核心技巧,你的FastAPI代码质量将立刻上一个台阶:
- 重复代码抽成依赖——提升复用,减少冗余。
- 资源管理用
yield——安全高效,避免泄露。
- 测试时直接传 Mock——隔离依赖,测试无忧。
在云栈社区与更多开发者交流,能帮助你更深入地掌握这些实践。优雅的代码并非一蹴而就,但通过运用像依赖注入这样好的方法和模式,可以让你的开发之路更加顺畅。