用 Python 打造你的专属 IOC 容器

前言

最近一段时间在学习一些和 Python 有关的东西,但是对 langflow 这个框架有了一个比较清晰的了解。

并且在阅读源码的过程当中发现了一些之前写 Python 都没有想到的设计,所以写这篇文章的目的是记录一下自己在阅读源码的过程当中的收获,并且成功的创建了一个属于自己的 Python 开发模板方便以后自己使用。

本文接下来主要介绍怎么样使用 Python 来给自己的项目添加一个简单的 IOC 容器,并且能够按照规范自动的将指定的 service 注册到容器当中,省去手动管理依赖的繁琐,让整个开发流程更高效、更解耦。

基于本篇文章介绍的 IOC 容器,开源了一个 Python Web 的开发模板,欢迎大家参考或者 star: github.com/DimplesY/fa...

IOC 容器带来的代码变革

当然,按照惯例,我先给大家展示一下自己实现的 IOC 容器和自己对项目当中各种 service 进行初始化的区别。

假设我们的项目下有两个 Service: 分别是 UserService 和 DatabaseService,按照正常的开发来说,再使用 UserService 的时候比如创建用户、查询用户等等操作肯定都需要依赖到DatabaseService。

也就是按照我们自己的手动去创建 UserService 实例之前肯定需要先将 DatabaseService 的实例创建出来。

代码就如下面所示:

python 复制代码
database_service = DatabaseService()

# UserService 需要使用 DatabaseService 来对数据库进行一些操作
user_service = UserService(database_service)

使用了 IOC 容器之后,容器会在创建 UserService 实例的时候自动检查当前应用的 IOC 容器当中是否存在 DatabaseService 的实例,如果存在直接使用,如果不存在会帮我们创建出来 UserService 的实例放入到 IOC 容器当中,自动解析 UserService 的依赖进行实例化。

在使用上就像下面的代码这样,直接通过 service 的名字来从 IOC 容器当中获取 service 的实例。

python 复制代码
user_service = get_service("user_service")

通过一个简单的 get_service 操作就获取到了具体的 Service 实例,使用非常方便,并且不需要开发者自己来维护不同的 service 之间的关系。

项目结构设计

实现的 IOC 容器代码目录结构如下:

bash 复制代码
ioc # 容器根目录
├── base.py # Service 抽象类
├── factory.py # Service 抽象工厂
├── manager.py # Service Manager
├── schema.py # 定义 Service 的名字
├── services # 具体的 Service 实现
│   ├── database
│   │   ├── factory.py
│   │   └── service.py
│   └── user
│       ├── factory.py
│       └── service.py
└── util.p # IOC 辅助方法

在开始介绍代码实现的原理之前,我们需要认识一个最基本的原理就是像上面的这种 IOC 容器底层本质都是一个字典或者多个字典的结构存储这各个依赖之间的关系。

基础工厂模式实现示例

当然,开始之前你也需要了解一些工厂模式这种建造模式,在实现 IOC 容器的过程当中需要用到。你只要能够看懂下面这种简单的代码就行。

python 复制代码
from ioc.factory import ServiceFactory
from ioc.services.database.service import DatabaseService

class DatabaseServiceFactory(ServiceFactory):
    """
    数据库服务工厂类。
    负责创建数据库服务实例。
    """
    def __init__(self) -> None:
        """
        初始化数据库服务工厂。
        """
        super().__init__(DatabaseService)

    def create(self):
        """
        创建数据库服务实例。
        
        返回:
            DatabaseService: 新创建的数据库服务实例
        """
        return DatabaseService()

编写 Service 抽象基类

我们先来制定一个 IOC 容器当中的 Service 基类,使用让 Service 继承 ABC 这个类,让它成为一个抽象类,并且在类的内部我们定义了三个函数。

  • get_schema 用于获取实例上的一些元信息
  • teardown 本文用不上做占位,实际作用是在 IOC 容器标记 service 失效的时候需要用到
  • set_ready 标记实例创建成功,可以从 IOC 容器当中获取使用

具体的代码如下:

python 复制代码
# base.py
from abc import ABC

class Service(ABC):
    """
    服务基类,所有服务都应继承此类。
    提供服务的基本功能和接口。
    """
    name: str  # 服务名称
    ready: bool = False  # 服务是否准备就绪的标志

    def get_schema(self):
        """
        获取服务的方法模式信息。
        返回一个包含服务所有公共方法的详细信息的字典。
        
        返回:
            dict: 包含方法名称、参数、返回类型和文档的字典
        """
        schema = {}
        ignore = ["teardown", "set_ready"]  # 忽略这些特殊方法
        for method in dir(self):
            if method.startswith("_") or method in ignore:
                continue  # 跳过私有方法和被忽略的方法
            func = getattr(self, method)
            schema[method] = {
                "name": method,  # 方法名称
                "parameters": func.__annotations__,  # 方法参数类型注解
                "return": func.__annotations__.get("return"),  # 返回类型注解
                "documentation": func.__doc__,  # 方法文档字符串
            }
        return schema

    async def teardown(self) -> None:
        """
        服务关闭时的清理方法。
        子类可以重写此方法以实现自定义清理逻辑。
        
        返回:
            None
        """
        return

    def set_ready(self) -> None:
        """
        将服务标记为准备就绪。
        通常在服务初始化完成后调用。
        
        返回:
            None
        """
        self.ready = True

实现 Service 抽象工厂

有了 Service 的抽象类以后,我们还需要一个 Service 抽象工厂,定义一些 Service 的通用行为。

python 复制代码
# factory.py

from __future__ import annotations

import importlib
import inspect
from typing import get_type_hints

from ioc.base import Service
from ioc.schema import ServiceType


class ServiceFactory:
    """
    服务工厂基类,用于创建服务实例。
    负责管理服务的依赖关系和实例化过程。
    """
    def __init__(
            self,
            service_class,
    ) -> None:
        """
        初始化服务工厂。
        
        参数:
            service_class: 要创建的服务类
        """
        self.service_class = service_class
        # 推断服务依赖关系
        self.dependencies = infer_service_types(self, import_all_services_into_a_dict())

    def create(self, *args, **kwargs) -> Service:
        """
        创建服务实例的方法。
        子类应该重写此方法以实现具体的创建逻辑。
        
        参数:
            *args: 位置参数
            **kwargs: 关键字参数
            
        返回:
            Service: 创建的服务实例
        """
        raise self.service_class(*args, **kwargs)


def infer_service_types(factory: ServiceFactory, available_services=None) -> list[ServiceType]:
    """
    推断服务工厂创建方法所需的依赖服务类型。
    
    参数:
        factory: 服务工厂实例
        available_services: 可用服务的字典
        
    返回:
        list[ServiceType]: 依赖服务类型列表
    """
    create_method = factory.create
    # 获取创建方法的类型提示
    type_hints = get_type_hints(create_method, globalns=available_services)
    service_types = []
    for param_name, param_type in type_hints.items():
        if param_name == "return":
            continue  # 跳过返回类型

        # 将参数类型名称转换为ServiceType枚举名称
        type_name = param_type.__name__.upper().replace("SERVICE", "_SERVICE")

        try:
            # 尝试获取对应的ServiceType枚举值
            service_type = ServiceType[type_name]
            service_types.append(service_type)
        except KeyError as e:
            msg = f"No matching ServiceType for parameter type: {param_type.__name__}"
            raise ValueError(msg) from e
    return service_types


def import_all_services_into_a_dict():
    """
    导入所有服务类到一个字典中。
    
    返回:
        dict: 服务名称到服务类的映射字典
    """
    from ioc.base import Service

    services = {}
    # 遍历所有服务类型
    for service_type in ServiceType:
        try:
            # 从服务类型获取服务名称
            service_name = ServiceType(service_type).value.replace("_service", "")
            module_name = f"ioc.services.{service_name}.service"
            # 导入服务模块
            module = importlib.import_module(module_name)
            # 更新服务字典
            services.update(
                {
                    name: obj
                    for name, obj in inspect.getmembers(module, inspect.isclass)
                    if issubclass(obj, Service) and obj is not Service
                }
            )
        except Exception as exc:
            msg = "Could not initialize services. Please check your settings."
            raise RuntimeError(msg) from exc
    return services

服务类型的枚举(schema.py

创建一个供程序识别哪些 service 是需要交给 IOC 容器管理的枚举,如下代码所示,和示例的项目一样,定义了 user_service 和 database_service 两个 service 。

python 复制代码
# schema.py
from enum import Enum


class ServiceType(str, Enum):
    """
    服务类型枚举。
    定义系统中所有可用的服务类型。
    每个枚举值对应一个服务的唯一标识符。
    """
    USER_SERVICE = "user_service"  # 用户服务
    DATABASE_SERVICE = "database_service"  # 数据库服务

容器管理器:ServiceManager

有了上面的定义之后,接下来需要实现一个 manager 来管理整个 IOC 容器,并且在初始化的它的时候能够自动把我们需要创建的 Service 所对应的工厂都建立起关系,代码如下所示:

python 复制代码
# manager.py
from ioc.schema import ServiceType
import importlib
import inspect

from ioc.base import Service
from ioc.factory import ServiceFactory


class ServiceManager:
    """
    服务管理器类,负责管理所有服务实例和工厂。
    实现了服务的依赖注入、懒加载和生命周期管理。
    """

    def __init__(self) -> None:
        """
        初始化服务管理器。
        创建服务和工厂的存储容器,并注册所有可用的工厂。
        """
        self.services: dict[str, Service] = {}  # 存储服务实例的字典
        self.factories: dict[str, ServiceFactory] = {}  # 存储服务工厂的字典
        self.register_factories()  # 注册所有可用的工厂

    def register_factories(self) -> None:
        """
        注册所有可用的服务工厂。
        遍历所有工厂并将它们注册到管理器中。
        """
        for factory in self.get_factories():
            try:
                self.register_factory(factory)
            except Exception:
                print(f"Failed to register factory: {factory}")

    def register_factory(
            self,
            service_factory: ServiceFactory,
    ) -> None:
        """
        注册单个服务工厂。
        
        参数:
            service_factory: 要注册的服务工厂实例
        """
        service_name = service_factory.service_class.name
        self.factories[service_name] = service_factory

    def get(self, service_name: ServiceType, default: ServiceFactory | None = None) -> Service:
        """
        获取指定名称的服务实例。
        如果服务尚未创建,则创建它。
        
        参数:
            service_name: 服务类型
            default: 默认工厂,如果没有注册的工厂则使用此工厂
            
        返回:
            Service: 服务实例
        """
        if service_name not in self.services:
            self._create_service(service_name, default)
        return self.services[service_name]

    def _create_service(self, service_name: ServiceType, default: ServiceFactory | None = None) -> None:
        """
        创建指定名称的服务实例。
        处理服务依赖关系的递归创建。
        
        参数:
            service_name: 服务类型
            default: 默认工厂,如果没有注册的工厂则使用此工厂
        """
        self._validate_service_creation(service_name, default)

        # 获取服务工厂
        factory = self.factories.get(service_name)
        if factory is None and default is not None:
            self.register_factory(default)
            factory = default
        if factory is None:
            msg = f"No factory registered for {service_name}"
            raise RuntimeError(msg)
            
        # 递归创建依赖服务
        for dependency in factory.dependencies:
            if dependency not in self.services:
                self._create_service(dependency)

        # 收集依赖服务实例
        dependent_services = {dep.value: self.services[dep] for dep in factory.dependencies}

        # 创建服务实例并标记为就绪
        self.services[service_name] = self.factories[service_name].create(**dependent_services)
        self.services[service_name].set_ready()

    def _validate_service_creation(self, service_name: ServiceType, default: ServiceFactory | None = None) -> None:
        """
        验证服务创建的前提条件。
        
        参数:
            service_name: 服务类型
            default: 默认工厂
        
        异常:
            RuntimeError: 如果没有注册的工厂且没有提供默认工厂
        """
        if service_name not in self.factories and default is None:
            msg = f"No factory registered for the service class '{service_name.name}'"
            raise RuntimeError(msg)

    @staticmethod
    def get_factories():
        """
        获取所有可用的服务工厂实例。
        
        返回:
            list: 服务工厂实例列表
        """
        from ioc.factory import ServiceFactory
        from ioc.schema import ServiceType

        # 从ServiceType枚举获取所有服务名称
        service_names = [ServiceType(service_type).value.replace("_service", "") for service_type in ServiceType]
        base_module = "ioc.services"
        factories = []

        # 遍历所有服务名称,导入对应的工厂模块
        for name in service_names:
            try:
                module_name = f"{base_module}.{name}.factory"
                module = importlib.import_module(module_name)

                # 查找并实例化工厂类
                for _, obj in inspect.getmembers(module, inspect.isclass):
                    if issubclass(obj, ServiceFactory) and obj is not ServiceFactory:
                        factories.append(obj())
                        break

            except Exception as exc:
                msg = f"Could not initialize services. Please check your settings. Error in {name}."
                raise RuntimeError(msg) from exc

        return factories


# 全局服务管理器实例
service_manager = ServiceManager()

最后再创建一个工具函数,方便我们从 IOC 容器当中取 Service,具体代码如下:

python 复制代码
from ioc.base import Service
from ioc.factory import ServiceFactory
from ioc.manager import service_manager
from ioc.schema import ServiceType


def get_service(service_name: ServiceType, default: ServiceFactory | None = None) -> Service:
    """
    获取指定类型的服务实例。
    这是一个便捷函数,直接调用全局服务管理器的get方法。
    
    参数:
        service_name: 要获取的服务类型
        default: 默认工厂,如果没有注册的工厂则使用此工厂
        
    返回:
        Service: 服务实例
    """
    return service_manager.get(service_name, default)

总结

ok,到这里为止,我们已经成功创建出来了自己的 IOC 容器,其中的 UserService 依赖了 DatabaseService,并且 UserService 当中的 create_user 方法会打印 DatabaseService 在 IOC 容器当中的实例,如下图所示:

文章的最后,让我们来测试一下自己创建的 IOC 容器,创建一个 main.py,代码如下所示:

python 复制代码
from ioc.schema import ServiceType  
from ioc.services.user.service import UserService  
from ioc.util import get_service  
  
if __name__ == "__main__":  
    user_service: UserService = get_service(ServiceType.USER_SERVICE)  
    print(user_service.create_user())

效果如下,非常完美,成功的打印出如下内容。

文章当中所有的代码,可以在这个代码仓库当中找到。

cnb.cool/mwlabs.tech...

相关推荐
Wgllss8 分钟前
完整案例:Kotlin+Compose+Multiplatform之桌面端音乐播放器,数据库使用实现(三)
android·架构·android jetpack
若梦plus13 分钟前
Xata低代码服务器端数据库平台之技术分析
前端·后端
若梦plus15 分钟前
Xano低代码后端开发平台之技术分析
前端·后端
柊二三27 分钟前
spring boot开发中的资源处理等问题
java·spring boot·后端
l1t35 分钟前
利用DeepSeek改写并增强测试Duckdb和sqlite的不同插入方法性能
python·sql·sqlite·duckdb
曾经的三心草1 小时前
微服务的编程测评系统10-竞赛删除发布-用户管理-登录注册
微服务·云原生·架构
GetcharZp2 小时前
Weaviate从入门到实战:带你3步上手第一个AI应用!
人工智能·后端·搜索引擎
ID_180079054732 小时前
python采集拍立淘按图搜索API接口,json数据参考
大数据·数据库·python·json
不是二师兄的八戒3 小时前
PDF转图片工具技术文档(命令行版本)
前端·python·pdf