python利用依赖注入实现模块解耦
python不是编译型语言, 比较容易出现循环依赖的情况, 比如模块A依赖模块B, 而模块B反过来依赖模块A. 当然可以通过重构解决此问题, 比如合并此两个模块. 但是还有一些技术可以帮助实现解耦. 比如之前我写过的基于消息的机制, 把模块间的依赖转换为对消息的依赖. 本文章介绍另外一种技术: 依赖注入.
关于依赖注入的开源库有不少, 比如:
https://github.com/google/pinject
https://github.com/python-injector/injector
https://github.com/ets-labs/python-dependency-injector
不过我们可以自己实现一个简单的单例模式, 所以就不用这些开源组件了.
我们的实现代码如下:
from typing import TypeVar
T = TypeVar('T') # 范型类型
_instances:list[type,object] = {}
'''对象实例字典. key为对象类型, object为对象实例'''
def get_obj_by_type(class_name:T,*args,**kwargs)->T|None:
'''获得指定类型的对象全局单例. args和kwargs为对象所需的参数. 如果创建对象失败, 返回None'''
#if not hasattr(class_name, '_is_single_sevice'): raise Exception('20231205_1216: 类必须设置单例属性.')
if class_name not in _instances:
try: _instances[class_name] = class_name(*args,**kwargs)
except: return None
return _instances[class_name]
def single_sevice(cls:T)->T:
'''单例装饰器. 装饰类, 使其成为全局单例'''
cls._is_single_sevice:bool = True # 设置为单例服务
return cls
def get_obj(module_name:str,class_name:str,*args,**kwargs)->object:
'''获得指定模块的指定类对象. args和kwargs为对象所需的参数. 如果创建对象失败, 返回None'''
module = __import__(module_name)
m = getattr(module,class_name)
return get_obj_by_type(m,*args,**kwargs)
用户有几种用法:
- 根据类型获得对象
from DataMgr import DataMgr
from my_injector import get_obj_by_type
obj = get_obj_by_type(DataMgr)
obj.run()
这种方式比较简单直接, 并且支持类型提示和自动补全, 但是显式依赖于被调用模块, 仍可能造成循环依赖.
- 根据模块名和类名获得对象
obj = get_obj('DataMgr','DataMgr')
obj.run()
这种方式比较简单粗暴, 不显式依赖服务组件, 但是不支持类型提示和代码补全.
- 结合什么两种模式的优点, 提供服务封装
from __future__ import annotations
from typing import TYPE_CHECKING
from my_injector import get_obj
if TYPE_CHECKING:
from DataMgr import DataMgr
from OperateUnit import UnitMgr
def get_DataMgr()->DataMgr: return get_obj('DataMgr','DataMgr')
def get_UnitMgr()->UnitMgr: return get_obj('OperateUnit','UnitMgr')
由于使用了TYPE_CHECKING技术, 既支持类型提示有不真的依赖于被调用组件. 用户可以在任何地方放心使用类似get_DataMgr().run()
的代码即可.
这样, 结合消息模式和依赖注入技术, 可以最大限度的减少python模块间的依赖, 使代码更容易开发和维护.