python利用依赖注入实现模块解耦

发布时间 2023-12-06 20:28:45作者: 顺其自然,道法自然

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模块间的依赖, 使代码更容易开发和维护.