Python 依赖注入(DI)三种实现方式与可测试性实践
概述
依赖注入(Dependency Injection,简称 DI)是解决大型项目中依赖关系混乱、代码难以测试和维护的关键模式。它将对象的创建与使用分离,使代码更松耦合、更易扩展。结合类型提示(typing + mypy)和现代框架,Python 能优雅承载所有 DI 模式。
基础概念
依赖注入的实现离不开 Python 核心语法与面向对象机制。
- 核心语法与数据类型:列表、字典、集合、元组是构建依赖容器的基础;条件语句、循环和异常处理确保注入逻辑健壮。
config = {"db_url": "sqlite:///test.db", "timeout": 30}
items = [1, 2, 3]
- 函数与面向对象编程:函数定义、参数传递、匿名函数(lambda)和装饰器是 DI 的基础工具。类通过构造方法接收依赖,实现封装与多态。
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 花费时间:{end - start:.4f}秒")
return result
return wrapper
@timer
def compute_sum(n):
return sum(range(n))
print(compute_sum(1000000))
主流实现方式
Python 提供三种主流 DI 方式,每种都对应不同复杂度场景。
1. 显式传参(Constructor Injection / Function Injection)
最简单、最推荐的纯 Python 方式:直接在构造方法或函数参数中传入依赖。
class PaymentService:
def __init__(self, db: Database, email_sender: EmailSender):
self.db = db
self.email_sender = email_sender
def process_payment(self, order_id: int, amount: float):
self.db.save_transaction(order_id, amount)
self.email_sender.send_confirmation(order_id)
2. 工厂函数(Factory Pattern)
工厂函数负责组装依赖,适合配置驱动场景。
def create_payment_service() -> PaymentService:
db = Database(config["db_url"])
email = SmtpEmailSender(config["smtp_host"])
return PaymentService(db, email)
3. 容器框架(Dependency Injector / FastAPI Depends)
使用第三方容器自动解析依赖,适合大型项目。
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
db = providers.Singleton(Database, url=config["db_url"])
email_sender = providers.Singleton(SmtpEmailSender, host=config["smtp_host"])
payment_service = providers.Factory(PaymentService, db=db, email_sender=email_sender)
container = Container()
service = container.payment_service()
代价权衡
三种方式各有权衡,选择取决于项目规模。
- 显式传参:优势为零魔术、高可读、易静态检查;代价为上层代码需层层传递依赖。适用于单元测试友好场景。
- 工厂函数:优势为集中管理创建逻辑,便于切换实现;代价为引入一层间接性,调试时需追踪工厂调用链。适用于配置动态加载场景。
- 容器框架:优势为自动解析、支持作用域、配置外部化;代价为学习曲线陡峭、运行时魔术、可能隐藏依赖关系。适用于微服务或大型系统。
总体代价排序(由低到高):显式传参 < 工厂函数 < 容器框架。
实战案例:提升可测试性
以电商'订单支付'模块为例,传统写法常把依赖藏进全局单例,导致测试时无法 mock、产生副作用。
错误示范(全局单例)
# bad.py
global_db = Database()
class PaymentService:
def process(self, order_id):
global_db.save(...)
正确重构(显式传参 + 工厂 + 测试友好)
- 领域层(纯净、无全局)
class PaymentService:
def __init__(self, db: Database, notifier: Notifier):
self.db = db
self.notifier = notifier
def process_payment(self, order_id: int, amount: float):
transaction = Transaction(order_id, amount)
self.db.save(transaction)
self.notifier.send("支付成功", order_id)
- 工厂 + 上下文管理(生产环境)
from contextlib import contextmanager
@contextmanager
def get_payment_service():
db = Database(get_db_url())
notifier = EmailNotifier()
service = PaymentService(db, notifier)
try:
yield service
finally:
db.close()
- FastAPI 应用层集成(异步 DI)
from fastapi import Depends, FastAPI
def get_db():
db = Database()
try:
yield db
finally:
db.close()
app = FastAPI()
@app.post("/pay")
def pay(order_id: int, service: PaymentService = Depends(lambda: PaymentService(get_db(), EmailNotifier()))):
service.process_payment(order_id, 99.9)
单元测试案例(pytest + mocks)
def test_payment_success():
mock_db = Mock()
mock_notifier = Mock()
service = PaymentService(mock_db, mock_notifier)
service.process_payment(123, 99.9)
mock_db.save.assert_called_once()
mock_notifier.send.assert_called_once_with("支付成功", 123)
总结
依赖注入在 Python 中高度实用:显式传参提供透明基础,工厂函数实现灵活组装,容器框架支撑大规模自动化。通过电商支付案例,可以看到它如何彻底解决全局单例问题,让代码真正可测、可维护。建议根据项目规模选择合适模式,优先保证依赖关系透明化。

