什么是依赖注入
开发中经常遇到一个对象需要另一个对象来完成工作的情况。比如,一个订单服务需要数据库连接才能保存数据。如果每次都在类内部直接创建数据库连接,那这个类就和具体的数据库实现绑死了。
依赖注入就是把“需要的东西”从外面传进去,而不是在类里面自己 new 一个。这样代码更灵活,测试也更容易。
手动实现依赖注入
Python 没有强制的类型系统,实现依赖注入可以很轻量。比如写一个简单的邮件服务:
class EmailService:
def send(self, to: str, message: str):
print(f"发送邮件给 {to}:{message}")
class NotificationService:
def __init__(self, email_service):
self.email_service = email_service
def notify(self, user):
self.email_service.send(user, '您的订单已创建')
# 使用时从外部传入依赖
email_svc = EmailService()
notifier = NotificationService(email_svc)
notifier.notify('alice@example.com')这里 NotificationService 不关心 email_service 到底是哪种实现,只要它有 send 方法就行。测试的时候完全可以传一个模拟对象。
用字典做简单的容器
项目大了以后,手动传参太麻烦。可以用一个字典管理所有服务实例,相当于一个简易的 IoC 容器:
class Container:
def __init__(self):
self._registry = {}
def register(self, key, instance):
self._registry[key] = instance
def get(self, key):
return self._registry[key]
# 注册服务
container = Container()
container.register('email_service', EmailService())
container.register('notifier', NotificationService(container.get('email_service')))
# 使用
notifier = container.get('notifier')
notifier.notify('bob@example.com')使用第三方库:Dependency Injector
真实项目中推荐用成熟的库,比如 Dependency Injector。它支持工厂、单例、配置注入等模式。
先安装:pip install dependency-injector
from dependency_injector import containers, providers
from dependency_injector.injectors import inject
class Container(containers.DeclarativeContainer):
email_service = providers.Factory(EmailService)
notifier = providers.Factory(
NotificationService,
email_service=email_service
)
container = Container()
@inject
def create_order(notifier=container.notifier):
notifier().notify('charlie@example.com')
create_order()这种方式让依赖关系集中管理,换实现时改一行代码就行。比如换成短信通知,只要替换注册部分,业务逻辑不用动。
配置注入也很实用
很多服务依赖配置项,比如数据库地址、API 密钥。Dependency Injector 也支持从环境变量或文件加载配置:
class ConfigContainer(containers.DeclarativeContainer):
config = providers.Configuration()
db_url = config.db.url.as_string()
debug = config.debug.as_bool()然后在启动时加载配置:
container = ConfigContainer()
container.config.from_dict({
'db': {'url': 'sqlite:///local.db'},
'debug': True
})这样一来,开发、测试、生产环境切换变得非常清晰。