昨天介绍了依赖注入的初步概念,可能有些同学觉得有点抽象。其实,它并没有想象中那么复杂。
今天,我们用3分钟,通过一个生活化的比喻,帮你把这个能让代码结构更清晰的核心概念彻底弄明白。
在深入学习FastAPI的依赖注入之前,我们有必要先理解几个基础概念,这样才能事半功倍。
第一个是函数参数传递:当你编写一个函数时,它所需的外部资源或数据,应该通过参数传递进来,而不是在函数内部直接创建或获取。
第二个是装饰器模式:这是一种为函数“添加包装”的技术,可以在不修改原函数代码的前提下,为其扩展新的功能。
第三个是解耦思想:让代码的不同模块尽可能地保持独立,修改一个模块时,不会“牵一发而动全身”。这就像拼搭积木,各个部分松散耦合,可以灵活组合。
理解了这三个概念,依赖注入学起来就会顺畅很多。
如果觉得抽象,我们打个现实的比方:炒菜的过程
假设你中午想加个菜:‘宫保鸡丁’。
- 传统写法 = 这道菜完全由你亲手操办:从去菜市场买菜、回家洗菜切菜、起锅烧油翻炒、到最后装盘上桌,所有步骤亲力亲为。
- 依赖注入 = 你只需要向外卖平台下一个订单:“我要一份宫保鸡丁的净菜包”。平台会自动把切配好的鸡肉、蔬菜和调配好的酱料送上门,你只需要进行最后的翻炒步骤即可。
看出区别了吗?
- 传统写法:函数内部需要自己创建和组装所有依赖项(比如数据库连接、配置文件、工具类实例)。
- 依赖注入:函数只需要声明“我需要什么”,外部框架或容器会自动将这些准备好的“依赖”送进来,函数直接使用即可。
核心就是:声明依赖,而非创建依赖。
这就是依赖注入的本质。
注意:这里你声明需要的“依赖”,也并非凭空产生,它们要么是你自己预先编写好的,要么是框架或第三方库提供的。
拓展一下:IoC 和 DI 是什么关系?
很多人容易混淆IoC(控制反转)和DI(依赖注入),我们来理清一下:
- IoC (控制反转) 是一种宽泛的设计思想。它指的是将程序中对依赖对象的控制权,从程序内部“反转”到外部容器或框架。
- DI (依赖注入) 是实现IoC思想的一种具体技术手段。它通过参数、属性或构造函数等方式,将依赖对象“注入”到需要使用它的类或函数中。
简单说:IoC是理念,DI是实践。
FastAPI 采用的就是依赖注入这种实践方式。
回到“宫保鸡丁”的例子
我们要实现一个获取当前用户信息的功能:
❌ 传统写法(臃肿且职责不清)
from fastapi import FastAPI, Header, HTTPException
import jwt
app = FastAPI()
@app.get("/me")
def get_current_user(authorization: str = Header(None)):
# 1. 自己解析 JWT token
# 2. 自己创建数据库连接
# 3. 自己查询数据库验证用户是否存在
# 4. 还要记得自己关闭数据库连接
# ... 大量与核心业务无关的代码
pass
这个 get_current_user 函数就像那份需要你从买菜做起的“宫保鸡丁”菜谱,所有工序都堆在一起。
✅ 依赖注入写法(清晰且优雅)
from fastapi import FastAPI, Depends
from fastapi.security import HTTPBearer
# 1. 把 token 解析逻辑抽离成一个独立的“依赖”函数
async def get_current_user_id(token: str = Depends(HTTPBearer())):
# 专责解析token,返回用户ID
user_id = decode_jwt(token)
return user_id
# 2. 把数据库会话管理也抽成依赖(示意,实际常用 yield 管理生命周期)
async def get_db():
# 创建并返回一个数据库会话
db = SessionLocal()
try:
yield db
finally:
db.close()
# 3. 路径操作函数只需声明需要什么,并专注于核心业务逻辑
@app.get("/me")
async def get_current_user(
user_id: int = Depends(get_current_user_id),
db = Depends(get_db)
):
# 这里直接使用注入进来的 user_id 和 db 会话
user = db.query(User).filter(User.id == user_id).first()
return user
Depends() 函数就像是那个“外卖平台”的入口。你告诉它需要什么(get_current_user_id, get_db),它就会在调用你的函数之前,先运行这些依赖函数,并把结果“送”给你。
Depends() 的基本用法
FastAPI 实现依赖注入的核心就是 Depends() 函数,其用法非常简单:
1. 定义依赖函数
编写一个普通的函数(可以是异步 async def 或同步 def),让它返回你需要的值。
def get_query_param(q: str = None):
return q
2. 在路径操作函数中使用
在路径操作函数的参数列表中,使用 Depends(你的依赖函数) 作为默认值。
@app.get("/items/")
async def read_items(q: str = Depends(get_query_param)):
return {"q": q}
就这么简单!FastAPI 会自动调用 get_query_param 函数,并将其返回值赋给路径操作函数中的 q 参数。
常见误区与避坑指南
❌ 误区一:什么都要注入
并非所有函数调用都需要做成依赖。像一些纯计算的、无状态的工具函数,直接调用即可。过度设计会引入不必要的复杂性。在面向对象编程中,过度使用设计模式也可能导致类似问题。
❌ 误区二:依赖链过长
依赖可以嵌套(一个依赖本身可以声明它需要其他依赖),但应避免嵌套过深(例如超过3层)。过长的依赖链会使代码逻辑难以追踪,应考虑进行合理的拆分,保持代码的清晰度。
❌ 误区三:在依赖函数中编写业务逻辑
依赖函数应专注于提供资源或数据(如获取当前用户、创建数据库连接、验证权限)。核心的业务判断和处理逻辑应放在路径操作函数中。例如,get_current_user 只负责返回用户对象,而“检查用户是否有管理员权限”这个业务规则,应该在接口逻辑里判断。
总结
依赖注入不是用来炫技的复杂模式,而是一种让代码结构更清晰、更易维护的实用方法。
记住FastAPI开发中的三个关键技巧:

- 重复代码抽成依赖:将诸如身份验证、数据库会话获取等通用逻辑封装成依赖,避免代码重复。
- 资源管理用 yield:对于需要精确控制创建和销毁的资源(如数据库连接),在依赖函数中使用
yield,FastAPI能确保资源被正确清理。
- 测试时直接传 mock:由于依赖是注入的,在单元测试中,你可以轻松传入模拟(mock)对象来替换真实依赖,使得测试更加独立和高效。
优雅的代码往往不是通过硬编码写出来的,而是通过良好的设计和清晰的依赖关系“组织”出来的。掌握依赖注入,能帮助你在Web开发中构建出更健壮、更易测试的应用。