python實現(xiàn)一個通用的插件類
本文提供了一種插件類的實現(xiàn)方案。
定義插件管理器
插件管理器用于注冊、銷毀、執(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: 返回加載的模塊中的對象 """ 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ù)管理器 .""" # 存放注冊的可執(zhí)行對象 __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 ) ) # 存放類的實例 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: """清空注冊信息 .""" cls.__hub = {} @classmethod def all_funcs(cls) -> Dict: """獲得所有的注冊信息. """ return cls.__hub @classmethod def get_func(cls, func_name: str) -> Callable: """獲得注冊的函數(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í)行注冊的函數(shù) .""" 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 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
定義元類的一個抽象派生類
支持參數(shù)驗證。
class BaseInvocation(metaclass=InvocationMeta): """ Base class for function invocation """ class Inputs(BaseModel): """ 輸入校驗器 """ pass @validate_arguments # type: ignore def __call__(self, *args, **kwargs): # 輸入?yún)?shù)校驗, 僅可能是 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ù)校驗 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
單元測試
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): """ 輸入校驗器 """ 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): """ 輸入校驗器 """ 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實現(xiàn)一個通用的插件類的文章就介紹到這了,更多相關(guān)python 通用插件類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python?pandas修剪函數(shù)clip使用實例探究
在數(shù)據(jù)處理和分析中,經(jīng)常面臨著需要限制數(shù)據(jù)范圍的情況,而pandas庫提供的clip函數(shù)就是一個強大的工具,可以方便地對數(shù)據(jù)進行修剪,本文將深入介紹clip函數(shù)的基本用法、常見參數(shù)以及實際場景中的應(yīng)用,以幫助大家充分理解并靈活運用這一功能2024-01-01Python深度學(xué)習(xí)albumentations數(shù)據(jù)增強庫
下面開始albumenations的正式介紹,在這里我強烈建議英語基礎(chǔ)還好的讀者去官方網(wǎng)站跟著教程一步步學(xué)習(xí),而這里的內(nèi)容主要是我自己的一個總結(jié)以及方便英語能力較弱的讀者學(xué)習(xí)2021-09-09Python自動化操作Excel方法詳解(xlrd,xlwt)
Excel是Windows環(huán)境下流行的、強大的電子表格應(yīng)用。本文將詳解用Python利用xlrd和xlwt實現(xiàn)自動化操作Excel的方法詳細,需要的可以參考一下2022-06-06