Python 依赖注入(DI)三种实现方式与可测试性实践
Python 依赖注入通过分离对象创建与使用降低耦合。主要实现方式包括显式传参、工厂函数及容器框架。显式传参可读性强但参数多;工厂函数集中管理配置;容器框架适合大型系统但有学习成本。以电商支付场景为例,通过重构全局单例为显式注入,结合 FastAPI Depends 与单元测试 Mock,可显著提升代码覆盖率至 98% 以上并支持并行测试。推荐根据项目规模选择合适模式,优先保证依赖关系透明化。

Python 依赖注入通过分离对象创建与使用降低耦合。主要实现方式包括显式传参、工厂函数及容器框架。显式传参可读性强但参数多;工厂函数集中管理配置;容器框架适合大型系统但有学习成本。以电商支付场景为例,通过重构全局单例为显式注入,结合 FastAPI Depends 与单元测试 Mock,可显著提升代码覆盖率至 98% 以上并支持并行测试。推荐根据项目规模选择合适模式,优先保证依赖关系透明化。

依赖注入(Dependency Injection,简称 DI)是解决大型项目中依赖关系混乱、代码难以测试和维护的关键模式。它将对象的创建与使用分离,使代码更松耦合、更易扩展。结合类型提示(typing + mypy)和现代框架,Python 能优雅承载所有 DI 模式。
依赖注入的实现离不开 Python 核心语法与面向对象机制。
config = {"db_url": "sqlite:///test.db", "timeout": 30}
items = [1, 2, 3]
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 方式,每种都对应不同复杂度场景。
最简单、最推荐的纯 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)
工厂函数负责组装依赖,适合配置驱动场景。
def create_payment_service() -> PaymentService:
db = Database(config["db_url"])
email = SmtpEmailSender(config["smtp_host"])
return PaymentService(db, email)
使用第三方容器自动解析依赖,适合大型项目。
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()
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)
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 中高度实用:显式传参提供透明基础,工厂函数实现灵活组装,容器框架支撑大规模自动化。通过电商支付案例,可以看到它如何彻底解决全局单例问题,让代码真正可测、可维护。建议根据项目规模选择合适模式,优先保证依赖关系透明化。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online