Python 依赖注入实战指南

依赖注入让 Python 代码更可测、更易维护。本文从构造函数注入讲起,进阶到抽象接口、工厂函数装配,再到 DI 框架自动装配,层层递进。

最佳实践
并发任务的多路径协同插画

直接回答:依赖注入(DI)把"对象自己 new 依赖"变成"依赖从外部传入":耦合解除、测试时轻松替换为 Mock、实现可插拔。Python 没有内置 DI 框架,但语言足够灵活——手写构造函数注入就够用,复杂场景再上框架。

问题:硬编码的依赖

class OrderService:
    def __init__(self):
        self.gateway = StripeGateway()    # 焊死了

    def checkout(self, order):
        self.gateway.charge(order.total)

想换 PayPal?改源码。想测试?真扣款。这就是紧耦合的代价。

第一层:构造函数注入

class OrderService:
    def __init__(self, gateway):
        self.gateway = gateway            # 谁给用谁

    def checkout(self, order):
        self.gateway.charge(order.total)

# 生产
service = OrderService(StripeGateway())
# 测试
service = OrderService(FakeGateway())     # 断言调用记录即可

一行改动,可测性与灵活性全来。许多简单场景到这一层就足够,不必为了注入而引入容器。

第二层:抽象基类定义接口

from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    @abstractmethod
    def charge(self, amount: int) -> bool: ...

class StripeGateway(PaymentGateway):
    def __init__(self, api_key=None):
        self.api_key = api_key

    def charge(self, amount):
        raise NotImplementedError("请在业务适配器中实现支付调用")

class FakeGateway(PaymentGateway):
    def charge(self, amount):
        return True

构造函数标注 gateway: PaymentGateway——类型检查器帮你保证传入的是合格实现。

第三层:工厂函数统一装配

依赖多了以后,"组装"这件事值得集中:

import os

def build_order_service(env: str) -> OrderService:
    if env == "prod":
        return OrderService(StripeGateway(api_key=os.environ["STRIPE_KEY"]))
    if env in {"test", "local"}:
        return OrderService(FakeGateway())
    raise ValueError(f"未知运行环境: {env}")

装配逻辑一处管理,换实现改一行。这就是"穷人版 DI 容器",多数项目到此为止刚刚好。

第四层:DI 框架

当对象图复杂到手工装配成为负担,框架登场:

# dependency-injector 风格
from dependency_injector import containers, providers

class Container(containers.DeclarativeContainer):
    config = providers.Configuration()
    gateway = providers.Singleton(StripeGateway, api_key=config.stripe_key)
    service = providers.Factory(OrderService, gateway=gateway)

此处支付网关是接口示意,不会真实扣款;使用容器前需从受控配置设置 config.stripe_key。普通 Singleton 的首次创建并非线程安全,需要跨线程使用时选择线程安全 provider 或自行同步。

dependency-injector、injector 这类库提供声明式容器、作用域管理(单例/每次新建)、自动装配。框架的代价是魔法感增强——IDE 跳转与调试会变曲折,团队要有共识。

FastAPI 的启示

FastAPI 的 Depends 就是 DI 的成功实践:请求作用域的依赖(数据库会话、当前用户)声明即注入,测试时 dependency_overrides 整体替换。做 Web 项目时优先用好框架自带的 DI,别急着引第三方容器。

常见问题(FAQ)

Q:DI 和直接 import 模块有什么区别?
A:import 导入名称或模块,并不要求只能有一个实现;直接在业务内部构造具体依赖会增加替换难度,注入则把实现选择权交给调用方。需要替换性的依赖(网关、存储、时钟)注入,纯工具函数直接 import。

Q:什么信号说明该上 DI 框架了?
A:构造函数参数超过五六个、装配代码重复遍目皆是、需要请求/会话作用域的精细生命周期管理——三个中两个再考虑。

Q:DI 会让代码变绕吗?
A:手写注入不会(反而更直白);框架容器会有一点"装配在哪发生"的学习成本。收益是可测性与可替换性,大项目值回票价。

官方参考

本文基于官方文档整理,未进行运行时或性能测试。示例中的业务函数、数据模型和部署地址需结合项目补全;局部片段不等同于完整生产应用。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台