Python FastAPI框架内置了强大的依赖注入系统,通过Depends模块可以快速实现代码逻辑的解耦,让接口函数的职责更加单一,同时减少重复代码的编写。依赖注入的核心思想是将函数所需的外部依赖提前定义好,在调用时由框架自动传入,而不是在函数内部硬编码创建依赖对象。

什么是依赖注入
依赖注入是一种设计模式,指的是对象或者函数不需要自己创建所需的依赖,而是由外部容器在运行时提供这些依赖。在FastAPI中,依赖可以是一个函数、一个类实例,甚至是一个可调用对象,只要能被调用并返回对应的值即可。使用依赖注入可以带来几个明显的好处:
- 降低代码耦合度,各个模块的职责边界更清晰
- 提升代码复用性,通用的依赖逻辑可以多次复用
- 方便单元测试,注入依赖时可以替换为模拟对象
- 简化接口函数的逻辑,只保留核心业务处理代码
Depends模块的基础使用
FastAPI的Depends类用于声明依赖,使用方式非常简单,只需要在接口函数的参数中,将参数的默认值设置为Depends(依赖函数)即可。框架会在处理请求时自动调用依赖函数,将返回值传入接口函数。
下面是一个最基础的示例,定义一个获取当前用户信息的依赖,然后在接口中使用这个依赖:
from fastapi import FastAPI, Depends
app = FastAPI()
# 定义依赖函数,模拟获取当前登录用户
def get_current_user():
# 实际场景中这里会解析请求头中的token,查询数据库获取用户信息
return {"user_id": 1, "username": "test_user", "role": "admin"}
# 接口函数,参数user的默认值用Depends声明依赖
@app.get("/users/me")
def read_current_user(user: dict = Depends(get_current_user)):
return user
当访问/users/me接口时,FastAPI会自动调用get_current_user函数,将返回的字典赋值给user参数,接口直接返回用户信息即可,不需要在接口函数内部写获取用户的逻辑。
带参数的依赖定义
依赖函数也可以接收参数,这些参数同样可以由FastAPI自动注入,比如常见的请求参数、路径参数、查询参数等。下面的示例定义了一个校验查询参数的依赖,要求请求必须携带token参数,并且token不为空:
from fastapi import FastAPI, Depends, Query, HTTPException
app = FastAPI()
# 依赖函数接收查询参数token
def verify_token(token: str = Query(...)):
if not token:
raise HTTPException(status_code=400, detail="token不能为空")
# 实际场景中这里会校验token的合法性
return token
@app.get("/items/")
def read_items(token: str = Depends(verify_token)):
return {"message": "token校验通过", "token": token}
这里的verify_token依赖函数接收一个必填的查询参数token,FastAPI会先解析请求的查询参数,将token传入依赖函数,校验通过后再将token传给接口函数。如果token为空,依赖函数会直接抛出HTTP异常,不会执行接口函数的逻辑。
依赖嵌套使用
依赖之间也可以嵌套,即一个依赖函数可以依赖另一个依赖函数,FastAPI会自动处理依赖的调用顺序,先调用被依赖的函数,再调用上层依赖函数。比如我们可以把获取用户和校验用户权限拆成两个依赖:
from fastapi import FastAPI, Depends, HTTPException
app = FastAPI()
# 第一层依赖:获取当前用户
def get_current_user():
return {"user_id": 1, "username": "test_user", "role": "admin"}
# 第二层依赖:校验用户是否为管理员,依赖get_current_user
def verify_admin(user: dict = Depends(get_current_user)):
if user.get("role") != "admin":
raise HTTPException(status_code=403, detail="无管理员权限")
return user
@app.get("/admin/dashboard")
def admin_dashboard(user: dict = Depends(verify_admin)):
return {"message": "欢迎访问管理员面板", "user": user}
访问/admin/dashboard接口时,FastAPI会先调用get_current_user获取用户信息,再将用户信息传给verify_admin做权限校验,最后将校验通过的用户信息传给接口函数。这样的拆分让每个依赖的职责更单一,后续如果需要修改权限校验逻辑,只需要修改verify_admin函数即可。
类作为依赖使用
除了函数,类也可以作为依赖,只要类可以被调用(即实现了__call__方法),或者类的实例可以被调用。更常见的是将依赖定义为类,在类的__init__方法中接收参数,在__call__方法中实现依赖逻辑:
from fastapi import FastAPI, Depends
app = FastAPI()
class DBSession:
def __init__(self, db_url: str = "sqlite:///test.db"):
self.db_url = db_url
# 实际场景中这里会创建数据库连接
print(f"连接数据库:{self.db_url}")
def __call__(self):
# 返回数据库会话对象
return {"db_url": self.db_url, "session": "db_session_obj"}
# 声明类依赖,FastAPI会自动实例化DBSession类,然后调用实例
@app.get("/db/test")
def test_db(db: dict = Depends(DBSession())):
return {"message": "数据库连接成功", "db_info": db}
这种方式的优势是可以在实例化依赖类的时候传入配置参数,适合需要初始化的依赖场景,比如数据库连接、缓存客户端等。
全局依赖与路由组依赖
如果有多个接口都需要使用同一个依赖,不需要在每个接口的参数中都声明Depends,可以通过全局依赖或者路由组依赖的方式统一添加。全局依赖会在所有接口生效,路由组依赖只会在该路由组下的接口生效:
from fastapi import FastAPI, Depends, APIRouter
app = FastAPI()
# 定义通用依赖
def common_dependency():
return {"common_param": "全局通用参数"}
# 全局依赖,所有接口都会执行这个依赖
app.add_dependency(common_dependency)
# 定义路由组
router = APIRouter()
# 路由组级别的依赖,只有该路由组下的接口会执行
@router.get("/router/test")
def router_test():
return {"message": "路由组接口"}
app.include_router(router)
通过全局依赖和路由组依赖,可以进一步减少重复代码的编写,统一处理通用的前置逻辑,比如日志、请求统计、通用参数校验等。
依赖注入解耦代码的实践
实际开发中,我们可以将业务逻辑、数据查询、权限校验等逻辑都拆成依赖,接口函数只负责接收参数、调用依赖、返回结果,这样接口函数的代码会非常简洁,后续维护也更方便。比如一个创建订单的接口,我们可以拆分出以下依赖:
- 依赖1:解析请求头获取用户身份
- 依赖2:校验用户是否有创建订单的权限
- 依赖3:校验请求参数中的商品是否存在、库存是否充足
- 依赖4:获取数据库会话,执行订单入库操作
接口函数只需要接收这些依赖的返回值,组装返回结果即可,所有业务逻辑都在对应的依赖中实现,后续如果需要修改某一块逻辑,只需要修改对应的依赖函数,不会影响到其他逻辑。
注意:依赖函数的执行顺序是按照依赖的嵌套关系从上到下执行的,如果依赖中抛出了异常,后续的依赖和接口函数都不会执行,FastAPI会直接返回对应的错误响应。
通过合理使用FastAPI的Depends模块实现依赖注入,可以让代码结构更清晰,降低模块之间的耦合度,同时提升代码的复用性和可测试性,是FastAPI开发中非常重要的一个技巧。