python - 实现一个通用的插件类

本文提供了一种插件类的实现方案。

定义插件管理器

插件管理器用于注册、销毁、执行插件。


import abc
from functools import wraps
from typing import Callable, Dictfrom pydantic import (BaseModel,validate_arguments,ValidationError as PydanticValidationError,
)def import_string(dotted_path: str) -> Callable:"""Import a dotted module path and return the attribute/class designated by thelast name in the path. Raise ImportError if the import failed.Args:dotted_path: 字符串表示的模块类,module.classReturns:返回加载的模块中的对象"""try:module_path, class_name = dotted_path.rsplit(".", 1)except ValueError:raise ImportError("{} doesn't look like a module path".format(dotted_path))module: ModuleType = import_module(module_path)try:# 返回模块中的类return getattr(module, class_name)except AttributeError:raise ImportError('Module "{}" does not define a "{}" attribute/class'.format(module_path, class_name))class FunctionsManager:"""函数管理器 ."""# 存放注册的可执行对象__hub = {}  # type: ignore@classmethoddef register_invocation_cls(cls, invocation_cls: InvocationMeta, name=None) -> None:if not name:func_name = invocation_cls.Meta.func_nameelse:func_name = nameif not isinstance(func_name, str):raise ValueError(f"func_name {func_name} should be string")existed_invocation_cls = cls.__hub.get(func_name)if existed_invocation_cls:raise RuntimeError("func register error, {}'s func_name {} conflict with {}".format(existed_invocation_cls, func_name, invocation_cls))# 存放类的实例cls.__hub[func_name] = invocation_cls()@classmethoddef register_funcs(cls, func_dict) -> None:for func_name, func_obj in func_dict.items():if not isinstance(func_name, str):raise ValueError(f"func_name {func_name} should be string")if func_name in cls.__hub:raise ValueError("func register error, {}'s func_name {} conflict with {}".format(func_obj, func_name, cls.__hub[func_name]))if isinstance(func_obj, str):func = import_string(func_obj)elif isinstance(func_obj, Callable):func = func_objelse:raise ValueError("func register error, {} is not be callable".format(func_obj, func_name))cls.__hub[func_name] = func@classmethoddef clear(cls) -> None:"""清空注册信息 ."""cls.__hub = {}@classmethoddef all_funcs(cls) -> Dict:"""获得所有的注册信息. """return cls.__hub@classmethoddef get_func(cls, func_name: str) -> Callable:"""获得注册的函数 ."""func_obj = cls.__hub.get(func_name)if not func_obj:raise ValueError("func object {} not found".format(func_name))return func_obj@classmethoddef func_call(cls, func_name: str, *args, **kwargs):"""根据函数名执行注册的函数 ."""func = cls.get_func(func_name)return func(*args, **kwargs)

定义元类

派生的类可自行注册到插件管理器。

class InvocationMeta(type):"""Metaclass for function invocation"""def __new__(cls, name, bases, dct):# ensure initialization is only performed for subclasses of Pluginparents = [b for b in bases if isinstance(b, InvocationMeta)]if not parents:return super().__new__(cls, name, bases, dct)new_cls = super().__new__(cls, name, bases, dct)# meta validationmeta_obj = getattr(new_cls, "Meta", None)if not meta_obj:raise AttributeError("Meta class is required")func_name = getattr(meta_obj, "func_name", None)if not func_name:raise AttributeError("func_name is required in Meta")desc = getattr(meta_obj, "desc", None)if desc is not None and not isinstance(desc, str):raise AttributeError("desc in Meta should be str")# register funcFunctionsManager.register_invocation_cls(new_cls)return new_cls

定义元类的一个抽象派生类

支持参数验证。

class BaseInvocation(metaclass=InvocationMeta):"""Base class for function invocation"""class Inputs(BaseModel):"""输入校验器"""pass@validate_arguments  # type: ignoredef __call__(self, *args, **kwargs):# 输入参数校验, 仅可能是 args 或 kwargs 之一try:params = {}if args:inputs_meta = getattr(self.Inputs, "Meta", None)inputs_ordering = getattr(inputs_meta, "ordering", None)if isinstance(inputs_ordering, list):if len(args) > len(inputs_ordering):raise Exception(f"Too many arguments for inputs: {args}")params = dict(zip(inputs_ordering, args))elif kwargs:params = kwargs# 参数校验if params:self.Inputs(**params)except PydanticValidationError as e:raise Exception(e)# 执行自定义业务逻辑return self.invoke(*args, **kwargs)@abc.abstractmethoddef invoke(self, *args, **kwargs):"""自定义业务逻辑 ."""raise NotImplementedError()

定义装饰器

def register_class(name: str):def _register_class(cls: BaseInvocation):FunctionsManager.register_invocation_cls(cls, name=name)@wraps(cls)def wrapper():return cls()return wrapperreturn _register_classdef register_func(name: str):def _register_func(func: Callable):FunctionsManager.register_funcs({name: func})@wraps(func)def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapperreturn _register_func

单元测试

from pydantic import BaseModelfrom .register import FunctionsManager, register_func, register_class, BaseInvocation@register_func("add")
def add(x: int, y: int) -> int:return x + yclass Add(BaseInvocation):class Meta:func_name = "multiply"class Inputs(BaseModel):"""输入校验器"""x: inty: intclass Meta:ordering = ["x", "y"]def invoke(self, x: int, y: int) -> int:return x * y@register_class("subtract")
class Subtract:class Inputs(BaseModel):"""输入校验器"""x: inty: intclass Meta:ordering = ["x", "y"]def __call__(self, x: int, y: int) -> int:return x - yclass TestFunctionsManager:def test_register_func(self):func = FunctionsManager.get_func("add")assert func(2, 3) == 5def test_register_class(self):func = FunctionsManager.get_func("subtract")assert func(2, 3) == -1def test_metaclass(self):func = FunctionsManager.get_func("multiply")assert func(2, 3) == 6

参考

https://github.com/TencentBlueKing/bkflow-feel/blob/main/bkflow_feel/utils.py

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

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

相关文章

真·面试题总结——JVM虚拟机

JVM虚拟机 JVM虚拟机规范与实现 JVM虚拟机规范 JVM虚拟机实现 JVM的常见实现 JVM虚拟机物理架构 JVM虚拟机的运转流程 JVM类加载过程 JVM类加载器及类加载器类型 JVM类加载器双亲委派机制 JVM运行时数据区的内存模型 JVM运行时数据区的内存模型:程序计数器…

蓝桥杯第八届c++大学B组详解

目录 1.购物单 2.等差素数列 3.承压计算 4.方格分割 5.日期问题 6.包子凑数 7.全球变暖 8.k倍区间 1.购物单 题目解析&#xff1a;就是将折扣字符串转化为数字&#xff0c;进行相加求和。 #include<iostream> #include<string> #include<cmath> usin…

vue2 列表一般不使用索引删除的原因

在 Vue 中使用索引来删除列表项可能会导致一系列问题&#xff0c;尤其是在处理动态列表时。以下是一些可能的问题和相应的例子&#xff1a; 1. 数据不一致问题 当你使用索引来删除列表中的某个项时&#xff0c;如果列表中的其他项发生了变化&#xff08;比如新增或重新排序&a…

编译时提示存在多个默认构造函数的错误怎么解决呢?

c程序中&#xff0c;如果编译器提升存在多个默认构造函数怎么解决呢&#xff1f; class Date { public:Date(){_year 1900;_month 1;_day 1;}Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;} private:int _year;int _month;int _day…

chromium源码学习-调试日志 LOG

在学习 chromium 源码时&#xff0c;我们经常需要增加调试日志&#xff0c;常见的用法一般是 LOG(INFO) << "调试信息";其中 INFO 代表当前这条日志的级别&#xff0c;使用的时候就是输入 INFO 就行。接下来我们在探索下这个宏背后的内容。 一、基本用法 LO…

读所罗门的密码笔记08_共生思想(下)

1. 机器判断 1.1. 在生活的各个领域&#xff0c;机器正在我们无意识的情况下做出更多的决定 1.1.1. 我们看到的新闻会塑造我们的观点和行动&#xff0c;它们是根据我们过去行为中所表达的倾向&#xff0c;或者其他同类人的行为而生成的 1.2. …

K-均值聚类算法

K-均值聚类算法是一种常用的无监督学习算法&#xff0c;用于将数据集分成 K 个簇。该算法的主要思想是通过迭代的方式将数据点分配到离它们最近的簇中&#xff0c;并更新簇的中心点&#xff0c;直到满足某个停止条件为止。 以下是 K-均值聚类算法的基本步骤&#xff1a; 初始化…

【热门话题】WebKit架构简介

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 WebKit架构简介一、引言二、WebKit概览1. 起源与发展2. 模块化设计 三、WebCore…

Dubbo简介及基本作用

Dubbo 是一个高性能、轻量级的开源Java RPC框架。它提供了一套完整的服务治理方案&#xff0c;包括服务的提供、服务的发现、负载均衡、流量调度、服务监控等功能。Dubbo主要用于构建高效的微服务架构&#xff0c;主要面向企业中大型的分布式系统。 Dubbo 能做什么&#xff1a…

软考高级架构师:流水线的概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

基于SSM的“任务发布接收平台”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“任务发布接收平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 前台界面 收藏界面 留言管理界面 任务管理界面 订…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《考虑灵活性供需平衡的新型电力系统长短期储能联合规划》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

file_get_contents(‘php://input‘); 这个postman要如何传参

在 Postman 中传递参数给 file_get_contents(php://input); 是通过请求的 Body 部分来实现的。使用 Postman 进行 API 接口测试时&#xff0c;可以按照以下步骤来传递参数&#xff1a; 打开 Postman 并创建一个新的请求。在请求的 URL 地址栏输入你的 API 地址。选择请求方法为…

蓝桥杯刷题day13——自助餐【算法赛】

一、问题描述 食堂最近推出了自助取餐功能&#xff0c;可以通过盘子的形状自动计算费用。你参与到自助计算价格的项目工作中。视觉组的同学已经帮你通过图像识别把盘子图片转换为了字符串&#xff0c;你只需要计算具体的价格即可。 餐盘的费用如下表所示: 你将会得到n 个字符…

Spark实战:词频统计

文章目录 一、Spark实战&#xff1a;词频统计&#xff08;一&#xff09;Scala版1、分步完成词频统计2、一步搞定词频统计 &#xff08;二&#xff09;Python版1、分步完成词频统计2、一步搞定词频统计 二、实战总结 一、Spark实战&#xff1a;词频统计 &#xff08;一&#x…

WebKit内核架构深度解析:核心技术与工作机制

WebKit是一种开源的网页浏览器引擎&#xff0c;广泛应用于苹果Safari、谷歌Chrome&#xff08;早期版本&#xff09;以及其他诸多第三方浏览器中。其卓越的性能和跨平台特性使之在全球范围内具有广泛的影响力。本文将对WebKit的核心结构进行详尽的介绍&#xff0c;以便于读者深…

顶顶通呼叫中心中间件-话术编辑器机器人转人工坐席配置(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件-话术编辑器机器人转人工座席配置(mod_cti基于FreeSWITCH) 配置方法 一、ACD排队转接 二、伴随转接 比如你设置的通知规则是任意满足一个就通知那么通话时间设置为10 秒那样他只要通话时间到10秒他就会转坐席。 如果要转人工的时侯转手机可以这样配置 把…

CSS网页布局权威指南02 样式表内容

02 Stylesheet Contents CSS网页布局权威指南02 样式表内容 Inside a stylesheet, you’ll find a number of rules that look a little something like this: 在样式表中&#xff0c;你会发现许多规则看起来有点像这样: h1 {color: maroon;} body {background: yellow;}St…

Pointnet++改进即插即用系列:全网首发OREPA在线重新参数化卷积,替代普通卷积 |即插即用,提升特征提取模块性能

简介:1.该教程提供大量的首发改进的方式,降低上手难度,多种结构改进,助力寻找创新点!2.本篇文章对Pointnet++特征提取模块进行改进,加入OREPA,提升性能。3.专栏持续更新,紧随最新的研究内容。 目录 1.理论介绍 2.修改步骤 2.1 步骤一 2.2 步骤二 2.3 步骤三

【25考研】:四川大学计算机学院24届874考研考情分析

去年的考情分析也是我做的&#xff0c; 今年就在去年的基础上做了。保持形式不变&#xff0c;更改数据。 21考情&#xff1a; 万载月寒肠断客&#xff1a;四川大学计算机学院21届CS考研考情分析 22考情&#xff1a; 懒羊羊&#xff1a;四川大学计算机学院2022考研考情分析 2…