依赖注入与工厂
依赖注入(DI)的本意:依赖的创建和使用分离,通过外部传入。FastAPI 把 DI 做成了框架核心,理解它等于理解 FastAPI 的一半。工厂模式在 Python 里则简化为函数。
为什么需要 DI
# 反面: 内部自己创建, 耦合具体实现, 难测试
def handler():
db = MySQLConnection() # 换库要改代码, 测试要连真库
# 正面: 依赖从参数进来, 可替换
def handler(db: DBConnection):
...收益:解耦(依赖抽象)、可测试(注入 mock)、可配置(换实现不改调用方)。
FastAPI 的 DI 核心
依赖树: 嵌套解析, 自动注入
机制要点:
Depends()声明依赖,FastAPI 递归解析依赖树,自动注入参数- 依赖可以是函数、类、可调用对象;返回值就是注入值
- yield 依赖:
yield前的代码在请求开始执行,yield后在请求结束执行(清理数据库会话、关闭连接) - 依赖可以依赖依赖(嵌套),形成依赖树,FastAPI 处理缓存和生命周期
依赖覆盖:测试杀手锏
app.dependency_overrides 在不改业务代码的情况下替换依赖,是 FastAPI 测试的根基:
def fake_db():
yield MemoryDB()
app.dependency_overrides[get_db] = fake_db # 测试里替换
with TestClient(app) as client:
...不用 mock 框架,不用改代码,把“连数据库的依赖”换成“内存实现”即可。这体现了 DI 的核心价值:替换依赖而不动调用方。
工厂模式:函数即工厂
Python 的工厂不需要工厂类:
def make_client(env: str) -> Client: # 简单工厂
if env == "prod":
return ProdClient()
return DevClient()
make_prod = lambda: make_client("prod") # 带参工厂: 闭包/partial 固化参数- 简单工厂 = 一个函数,根据参数返回不同对象
- 工厂方法 = 函数内部多态创建(配 Protocol)
- 抽象工厂 = 返回一组相关对象的函数,业务中少见,别硬造
pydantic-settings
配置管理是 DI 的常见落点:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
debug: bool = False
@lru_cache
def get_settings() -> Settings:
return Settings() # 从环境变量/ .env 读取- 配置即依赖:通过
Depends(get_settings)注入到需要的地方,而不是到处 import @lru_cache保证进程内只读一次(单例作用域,见单例篇)- 环境变量自动映射,测试时改环境变量即可换配置
面试追问
- DI 解决什么? 依赖创建和使用分离。解耦、可测试(注入 mock)、可配置
- FastAPI 怎么实现 DI? Depends 声明依赖,框架递归解析依赖树自动注入。yield 依赖处理资源清理
- dependency_overrides 干什么? 测试时替换依赖,不改业务代码。FastAPI 测试的核心机制
- Python 工厂怎么写? 函数返回对象。带参工厂用闭包或 functools.partial,不需要工厂类
- pydantic-settings 的作用? 配置类从环境变量读取,lru_cache 保证单例,通过 Depends 注入。测试换环境变量即换配置