Python 实现最小插件框架

文章目录

  • Python 实现最小插件框架
    • 1. 基础实现
      • 项目结构
      • plugin_base.py - 插件基类
      • plugins/hello.py - 示例插件1
      • plugins/goodbye.py - 示例插件2
      • main.py - 主程序
    • 2. 更高级的特性扩展
      • 2.1 插件配置支持
      • 2.2 插件依赖管理
      • 2.3 插件热加载
    • 3. 使用 setuptools 的入口点发现插件
      • 3.1 修改项目结构
      • 3.2 setup.py 示例
      • 3.3 修改插件管理器
    • 4. 插件隔离(使用importlib)
    • 5. 最小完整示例(无目录结构要求)

Python 实现最小插件框架

一个非常简洁但功能完整的 Python 插件框架实现,这个框架具有以下特点:

  • 动态加载插件
  • 插件自动发现
  • 简单的插件接口
  • 支持插件隔离

1. 基础实现

项目结构

my_app/
├── main.py         # 主程序
├── plugins/        # 插件目录
│   ├── __init__.py
│   ├── hello.py    # 示例插件
│   └── goodbye.py  # 示例插件
└── plugin_base.py  # 插件基类

plugin_base.py - 插件基类

import abcclass PluginBase(abc.ABC):"""所有插件的基类"""@classmethod@abc.abstractmethoddef initialize(cls):"""插件初始化方法"""pass@abc.abstractmethoddef execute(self, *args, **kwargs):"""插件执行方法"""pass

plugins/hello.py - 示例插件1

from plugin_base import PluginBaseclass HelloPlugin(PluginBase):@classmethoddef initialize(cls):print("HelloPlugin initialized")return cls()def execute(self, name="World"):print(f"Hello, {name}!")

plugins/goodbye.py - 示例插件2

from plugin_base import PluginBaseclass GoodbyePlugin(PluginBase):@classmethoddef initialize(cls):print("GoodbyePlugin initialized")return cls()def execute(self, name="World"):print(f"Goodbye, {name}!")

main.py - 主程序

import importlib
import pkgutil
from pathlib import Path
from plugin_base import PluginBaseclass PluginManager:def __init__(self):self.plugins = {}def discover_plugins(self, plugin_dir="plugins"):"""自动发现插件"""plugin_path = Path(plugin_dir)# 遍历插件目录for finder, name, _ in pkgutil.iter_modules([str(plugin_path)]):try:module = importlib.import_module(f"{plugin_dir}.{name}")for item in dir(module):obj = getattr(module, item)if (isinstance(obj, type)and issubclass(obj, PluginBase)and obj != PluginBase):self.plugins[name] = objprint(f"Found plugin: {name}")except ImportError as e:print(f"Failed to import plugin {name}: {e}")def initialize_plugins(self):"""初始化所有插件"""return {name: plugin.initialize()for name, plugin in self.plugins.items()}if __name__ == "__main__":manager = PluginManager()manager.discover_plugins()plugins = manager.initialize_plugins()# 使用插件plugins["hello"].execute("Python")plugins["goodbye"].execute("Python")

2. 更高级的特性扩展

如果你想增强这个框架,可以考虑添加以下特性:

2.1 插件配置支持

修改 plugin_base.py:

class PluginBase(abc.ABC):@classmethod@abc.abstractmethoddef initialize(cls, config=None):"""支持传入配置"""pass

2.2 插件依赖管理

在插件类中添加:

class HelloPlugin(PluginBase):REQUIRED_PLUGINS = ['some_dependency']@classmethoddef initialize(cls):# 检查依赖pass

2.3 插件热加载

添加热加载方法:

def reload_plugin(self, plugin_name):"""重新加载插件"""if plugin_name in self.plugins:module = importlib.import_module(f"plugins.{plugin_name}")importlib.reload(module)self.plugins[plugin_name] = getattr(module, plugin_name.capitalize() + "Plugin")

3. 使用 setuptools 的入口点发现插件

更Pythonic的方式是使用setuptools的entry_points:

3.1 修改项目结构

my_app/
├── setup.py
├── my_app/
│   ├── __init__.py
│   ├── main.py
│   └── plugin_base.py
└── plugins/├── hello.py└── goodbye.py

3.2 setup.py 示例

from setuptools import setup, find_packagessetup(name="my_app",version="0.1",packages=find_packages(),entry_points={'my_app.plugins': ['hello = plugins.hello:HelloPlugin','goodbye = plugins.goodbye:GoodbyePlugin',],},
)

3.3 修改插件管理器

from importlib.metadata import entry_pointsclass PluginManager:def discover_plugins(self):"""使用entry_points发现插件"""discovered_plugins = entry_points(group='my_app.plugins')for ep in discovered_plugins:try:plugin_class = ep.load()if (isinstance(plugin_class, type)and issubclass(plugin_class, PluginBase)and plugin_class != PluginBase):self.plugins[ep.name] = plugin_classexcept Exception as e:print(f"Failed to load plugin {ep.name}: {e}")

4. 插件隔离(使用importlib)

如果需要更强的隔离,可以这样加载插件:

def load_plugin_with_isolation(plugin_path):"""隔离加载插件"""spec = importlib.util.spec_from_file_location("isolated_plugin", plugin_path)module = importlib.util.module_from_spec(spec)sys.modules["isolated_plugin"] = modulespec.loader.exec_module(module)return module

5. 最小完整示例(无目录结构要求)

如果你想要一个真正最小化的实现(单文件):

# mini_plugin.py
import importlib
from pathlib import Pathclass Plugin:def execute(self):raise NotImplementedErrorclass PluginManager:def __init__(self):self.plugins = {}def load_plugin(self, plugin_path):module_name = Path(plugin_path).stemspec = importlib.util.spec_from_file_location(module_name, plugin_path)module = importlib.util.module_from_spec(spec)spec.loader.exec_module(module)for name in dir(module):obj = getattr(module, name)if isinstance(obj, type) and issubclass(obj, Plugin) and obj is not Plugin:self.plugins[module_name] = obj()return obj()return Noneif __name__ == "__main__":manager = PluginManager()plugin = manager.load_plugin("hello_plugin.py")  # 假设同级目录下有这个文件if plugin:plugin.execute()

配套的最简插件文件 hello_plugin.py:

from mini_plugin import Pluginclass HelloPlugin(Plugin):def execute(self):print("Hello from minimal plugin!")

这个最小实现只有不到30行代码,但包含了插件框架的核心功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/78724.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电感详解:定义、作用、分类与使用要点

一、电感的基本定义 电感(Inductor) 是由导线绕制而成的储能元件,其核心特性是阻碍电流变化,将电能转化为磁能存储。 基本公式: 自感电动势: E -L * (di/dt) (L:电感值&#xff0c…

运行一次性任务与定时任务

运行一次性任务与定时任务 文章目录 运行一次性任务与定时任务[toc]一、使用Job运行一次性任务1.创建一次性任务2.测试一次性任务3.删除Job 二、使用CronJob运行定时任务1.创建定时任务2.测试定时任务3.删除CronJob 一、使用Job运行一次性任务 1.创建一次性任务 (…

对话记忆(Conversational Memory)

一、引言 在与大型语言模型(LLM)交互的场景中,对话记忆(Conversational Memory)指的是模型能够在多轮对话中保留、检索并利用先前上下文信息的能力。这一机制使得对话系统不再仅仅是“问答机”,而是能够持…

【HD-RK3576-PI】VNC 远程桌面连接

在当今数字化时代,高效便捷的操作方式是技术爱好者与专业人士的共同追求。对于使用 HD-RK3576-PI微型单板计算机的用户而言,当面临没有显示屏的场景时,如何实现远程操作桌面系统呢?别担心,VNC 远程桌面连接将为你解决这…

【unity游戏开发介绍之UGUI篇】UGUI概述和基础使用

注意:考虑到UGUI的内容比较多,我将UGUI的内容分开,并全部整合放在【unity游戏开发介绍之UGUI篇】专栏里,感兴趣的小伙伴可以前往逐一查看学习。 文章目录 前言1、UI系统的重要性2、UGUI概述2.1 基本定义2.2 UGUI发展历史 3、学习U…

Ubuntu 系统深度清理:彻底卸载 Redis 服务及残留配置

Ubuntu 系统深度清理:彻底卸载 Redis 服务及残留配置 在Ubuntu系统中,Redis是一种广泛使用的内存数据存储系统,用于缓存和消息传递等场景。然而,有时候我们需要彻底卸载Redis,以清理系统资源或为其他应用腾出空间。本…

[ARC196A] Adjacent Delete 题解

假设 n n n 是偶数。如果我们忽略删除相邻数的条件,即可以任选两个数相减,那么答案应该是前 n 2 \frac{n}{2} 2n​ 大的数(记作“较大数”)的和减去前 n 2 \frac{n}{2} 2n​ 小的数(记作“较小数”)的和…

Linux上位机开发实践(关于Qt的移植)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 linux平台上面,很多界面应用,都是基于qt开发的。不管是x86平台,还是arm平台,qt使用的地方都比较多。…

”插入排序“”选择排序“

文章目录 插入排序1. 直接插入排序(O(n^2))举例1:举例2:直插排序的"代码"直插排序的“时间复杂度” 2. 希尔排序(O(n^1.3))方法一方法二(时间复杂度更优) 选择排序堆排序直接选择排序 我们学过冒泡排序,堆排序等等。(回…

FPGA_BD Block Design学习(一)

PS端开发流程详细步骤 1.第一步:打开Vivado软件,创建或打开一个工程。 2.第二步:在Block Design中添加arm核心,并将其配置为IP核。 3.第三步:配置arm核心的外设信息,如DDR接口、时钟频率、UART接口等。 …

【Python] pip制作离线包

制作离线安装包是一种非常实用的方法,尤其是在网络环境受限或需要在多台机器上部署相同环境时。以下是详细的步骤,帮助您创建一个包含所有依赖项的离线安装包,并在后续环境中复用。 步骤 1:准备工具和环境 确保您有一台可以访问互…

为啥物联网用MQTT?

前言 都说物联网用MQTT,那分别使用Http和Mqtt发送“Hello”,比较一下就知道啦 HTTP HTTP请求报文由请求行、头部字段和消息体组成。一个最简单的HTTP POST请求如下: POST / HTTP/1.1 Host: example.com Content-Length: 5 Content-Type: …

操作系统 ------ 五种IO模型

阻塞IO:一个IO请求操作,准备阶段和复制阶段都会阻塞应用程序,直到操作完全完成 非阻塞IO:一个IO操作请求,先判断准备阶段是否完成,如果未完成立即返回,否则,进入复制阶段&#xff0…

service和endpoints是如何关联的?

在Kubernetes中,Service 和 Endpoints 是两个密切关联的对象,它们共同实现了服务发现和负载均衡的功能。以下是它们之间的关联和工作原理: 1. Service 的定义 Service 是一种抽象,定义了一组逻辑上相关的 Pod,以及用…

程序化广告行业(78/89):多因素交织下的行业剖析与展望

程序化广告行业(78/89):多因素交织下的行业剖析与展望 在程序化广告这片充满活力又不断变化的领域,持续学习和知识共享是我们紧跟潮流、实现突破的关键。一直以来,我都渴望能与大家一同探索这个行业的奥秘&#xff0c…

数智化重构供应商管理

当供应链韧性成为核心竞争力,你的供应商管理还在 “摸着石头过河” 吗? 在传统模式下,供应商管理高度依赖人工经验与纸质流程: 入库筛选如“大海捞针”:供应商资质审核停留在Excel表格比对,资质造假、历史…

网络互连与互联网

1.在路由表中找不到目标网络时使用默认路由,默认路由通常指本地网关的地址。 2.OSPF最主要的特征是使用分布式链路状态协议,而RIP使用的是距离向量协议。 3.OSPF使用链路状态公告LSA扩散路由信息 4.内部网关路由协议IGRP是一种动态距离矢量路由协议&a…

Raymarching Textures In Depth

本节课最主要的就是学会hlsl中使用纹理采样 float4 color Texture2DSample(Texobj, TexobjSampler, uv); return color; 课程中的代码(没有这张图我就没做) 课程代码产生深度的原因是uv偏移,黑色区域会不断向左偏移,直到找到白色…

【MQTT-协议原理】

MQTT-协议原理 ■ MQTT-协议原理■ MQTT-服务器 称为"消息代理"(Broker)■ MQTT协议中的订阅、主题、会话■ 一、订阅(Subscription)■ 二、会话(Session)■ 三、主题名(Topic Name&a…

docker容器安装的可道云挂接宿主机的硬盘目录:解决群晖 威联通 飞牛云等nas的硬盘挂接问题

基于Docker部署可道云(KodCloud)时,通过挂载宿主机其他磁盘目录可实现高效、安全的数据管理。具体而言,使用绑定挂载(Bind Mounts)将宿主机目录(如/data/disk2)映射到容器内的可道云…