python實(shí)現(xiàn)一個(gè)通用的插件類
本文提供了一種插件類的實(shí)現(xiàn)方案。
定義插件管理器
插件管理器用于注冊(cè)、銷毀、執(zhí)行插件。
import abc
from functools import wraps
from typing import Callable, Dict
from 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 the
last name in the path. Raise ImportError if the import failed.
Args:
dotted_path: 字符串表示的模塊類,module.class
Returns:
返回加載的模塊中的對(duì)象
"""
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:
"""函數(shù)管理器 ."""
# 存放注冊(cè)的可執(zhí)行對(duì)象
__hub = {} # type: ignore
@classmethod
def register_invocation_cls(cls, invocation_cls: InvocationMeta, name=None) -> None:
if not name:
func_name = invocation_cls.Meta.func_name
else:
func_name = name
if 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
)
)
# 存放類的實(shí)例
cls.__hub[func_name] = invocation_cls()
@classmethod
def 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_obj
else:
raise ValueError(
"func register error, {} is not be callable".format(
func_obj, func_name
)
)
cls.__hub[func_name] = func
@classmethod
def clear(cls) -> None:
"""清空注冊(cè)信息 ."""
cls.__hub = {}
@classmethod
def all_funcs(cls) -> Dict:
"""獲得所有的注冊(cè)信息. """
return cls.__hub
@classmethod
def get_func(cls, func_name: str) -> Callable:
"""獲得注冊(cè)的函數(shù) ."""
func_obj = cls.__hub.get(func_name)
if not func_obj:
raise ValueError("func object {} not found".format(func_name))
return func_obj
@classmethod
def func_call(cls, func_name: str, *args, **kwargs):
"""根據(jù)函數(shù)名執(zhí)行注冊(cè)的函數(shù) ."""
func = cls.get_func(func_name)
return func(*args, **kwargs)
定義元類
派生的類可自行注冊(cè)到插件管理器。
class InvocationMeta(type):
"""
Metaclass for function invocation
"""
def __new__(cls, name, bases, dct):
# ensure initialization is only performed for subclasses of Plugin
parents = [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 validation
meta_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 func
FunctionsManager.register_invocation_cls(new_cls)
return new_cls
定義元類的一個(gè)抽象派生類
支持參數(shù)驗(yàn)證。
class BaseInvocation(metaclass=InvocationMeta):
"""
Base class for function invocation
"""
class Inputs(BaseModel):
"""
輸入校驗(yàn)器
"""
pass
@validate_arguments # type: ignore
def __call__(self, *args, **kwargs):
# 輸入?yún)?shù)校驗(yàn), 僅可能是 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
# 參數(shù)校驗(yàn)
if params:
self.Inputs(**params)
except PydanticValidationError as e:
raise Exception(e)
# 執(zhí)行自定義業(yè)務(wù)邏輯
return self.invoke(*args, **kwargs)
@abc.abstractmethod
def invoke(self, *args, **kwargs):
"""自定義業(yè)務(wù)邏輯 ."""
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 wrapper
return _register_class
def 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 wrapper
return _register_func
單元測(cè)試
from pydantic import BaseModel
from .register import FunctionsManager, register_func, register_class, BaseInvocation
@register_func("add")
def add(x: int, y: int) -> int:
return x + y
class Add(BaseInvocation):
class Meta:
func_name = "multiply"
class Inputs(BaseModel):
"""
輸入校驗(yàn)器
"""
x: int
y: int
class Meta:
ordering = ["x", "y"]
def invoke(self, x: int, y: int) -> int:
return x * y
@register_class("subtract")
class Subtract:
class Inputs(BaseModel):
"""
輸入校驗(yàn)器
"""
x: int
y: int
class Meta:
ordering = ["x", "y"]
def __call__(self, x: int, y: int) -> int:
return x - y
class TestFunctionsManager:
def test_register_func(self):
func = FunctionsManager.get_func("add")
assert func(2, 3) == 5
def test_register_class(self):
func = FunctionsManager.get_func("subtract")
assert func(2, 3) == -1
def 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
到此這篇關(guān)于python實(shí)現(xiàn)一個(gè)通用的插件類的文章就介紹到這了,更多相關(guān)python 通用插件類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Django結(jié)合ajax進(jìn)行頁面實(shí)時(shí)更新的例子
今天小編就為大家分享一篇Django結(jié)合ajax進(jìn)行頁面實(shí)時(shí)更新的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-08-08
基于Python實(shí)現(xiàn)圖片批量加水印的方法
我們經(jīng)常有有這樣的經(jīng)歷:辛辛苦苦制作的原創(chuàng)圖片、精心拍攝的作品、耗費(fèi)心血設(shè)計(jì)的宣傳圖,剛發(fā)布到網(wǎng)上,沒過多久,就發(fā)現(xiàn)被別人盜用維 權(quán)耗時(shí)耗力,讓人沮喪,今天,我們將深入Python圖片處理的世界,手把手教你如何基于Python實(shí)現(xiàn)圖片批量加水印,需要的朋友可以參考下2025-11-11
python爬蟲搭配起B(yǎng)ilibili唧唧的流程分析
這篇文章主要介紹了python爬蟲搭配起B(yǎng)ilibili唧唧的流程分析,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
python range()函數(shù)取反序遍歷sequence的方法
今天小編就為大家分享一篇python range()函數(shù)取反序遍歷sequence的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-06-06
淺談Keras中shuffle和validation_split的順序
這篇文章主要介紹了淺談Keras中shuffle和validation_split的順序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-06-06
python調(diào)用自定義函數(shù)的實(shí)例操作
在本文里我們給大家整理了關(guān)于python調(diào)用自定義函數(shù)的實(shí)例操作相關(guān)內(nèi)容,有此需要的朋友們可以學(xué)習(xí)參考下。2019-06-06
Python中使用正則表達(dá)式及正則表達(dá)式匹配規(guī)則詳解
這篇文章主要介紹了Python中使用正則表達(dá)式以及正則表達(dá)式匹配規(guī)則,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
利用PyQt5模擬實(shí)現(xiàn)網(wǎng)頁鼠標(biāo)移動(dòng)特效
不知道大家有沒有發(fā)現(xiàn),博客園有些博客左側(cè)會(huì)有鼠標(biāo)移動(dòng)特效。通過移動(dòng)鼠標(biāo),會(huì)形成類似蜘蛛網(wǎng)的特效,本文將用PyQt5實(shí)現(xiàn)這一特效,需要的可以參考一下2022-03-03

