构造函数注入是将协作对象作为参数传入__init__,显式声明依赖关系以提升可测性与解耦度;必需依赖必须用此方式,避免隐藏依赖、硬编码或运行时异常。

构造函数注入就是把协作对象当参数传进去
Python 没有内置的 DI 容器,所谓“依赖注入”在大多数实际场景里,就是手动把协作对象(比如数据库连接、配置类、日志器)作为参数传给 __init__。这不是语法糖,而是显式表达依赖关系的设计选择——它让类不自己创建依赖,也不硬编码查找逻辑,可测性与解耦度立刻提升。
常见错误是写成这样:
class UserService:
def __init__(self):
self.db = DatabaseConnection() # ❌ 隐藏依赖,无法替换 mock
self.logger = logging.getLogger(__name__) # ❌ 硬编码,难测试
正确做法是:
class UserService:
def __init__(self, db: DatabaseConnection, logger: logging.Logger):
self.db = db
self.logger = logger
什么时候必须用构造函数注入而不是 setter 或属性赋值
当协作对象是类的**必需依赖**,且生命周期与宿主类一致时,构造函数注入最合理。比如一个 PaymentProcessor 必须持有有效的 PaymentGateway 才能工作,缺了就根本无法初始化。
Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。
-
__init__注入后,对象处于“就绪可用”状态,避免空引用或未初始化异常 - setter 注入(如
set_gateway())容易遗漏调用,导致运行时AttributeError - 属性直接赋值(
obj.gateway = ...)破坏封装,外部可随意篡改,且 IDE 和类型检查(mypy)难以推断 - 如果某个依赖是可选的(比如缓存客户端),才考虑用
Optional[CacheClient]+ 默认None,并在方法中做判空
如何避免构造函数参数爆炸
当协作对象超过 4–5 个,__init__ 就会变得难读、难维护,也违反单一职责原则。这不是注入方式的问题,而是类本身可能承担了太多职责。
- 优先拆分:把不同协作域抽成独立服务(例如把通知逻辑拆出
NotificationService),再注入该服务而非一堆底层组件 - 用配置对象聚合:比如定义
AppConfig类,把db_url、timeout、retry_policy封装进去,只注入一个config: AppConfig - 避免注入原始类型(如
str、int)——它们不是“协作对象”,应通过配置对象或环境变量获取 - 不推荐用
*args/**kwargs隐藏依赖,这会让类型提示失效,IDE 补全和 mypy 检查完全失效
类型提示和 mypy 能帮你发现哪些注入问题
Python 的类型提示不是装饰,它是构造函数注入的“契约说明书”。加上 mypy 后,能提前暴露三类典型问题:
- 传入了错误类型的实例:比如把
MockDatabase当作DatabaseConnection传入,但二者没有继承关系 → mypy 报Argument 1 to "UserService" has incompatible type ... - 漏传必需参数:调用
UserService()时不传db或logger→ mypy 提示Missing positional argument "db" - 协变/逆变误用:比如函数参数期望
Callable[[User], None],却传入Callable[[object], None]→ mypy 在泛型边界处报警 - 注意:如果用了
Any或没加类型提示,这些检查全部失效;Union过宽(如Union[DB1, DB2, DB3])也会削弱约束力
真正容易被忽略的是:构造函数注入本身不解决对象创建顺序问题。如果你在 __init__ 里又去 new 另一个服务,那就退化回了 new 耦合——注入的应该是已经构建好的实例,谁来构建、何时构建,得靠上层协调(比如工厂函数或简易容器),那才是 DI 的下一步。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










