🙆

Python で動的な plugin を実装したい

2023/10/23に公開

やりたいことは、plugins ディレクトリに作ったファイルを自動的に import してプラグインとして呼び出すということです。世間一般にどうやってプラグインを実装してるのかわからなくて自分で作ってみました。プラグインの追加削除をファイルの有無で動的に行うことを目指しました。

ここでは基底クラスを Parent としています。プラグインは Parent を継承している必要があります。call_method() ですべてのプラグインが持つ同名のメソッドを呼び出します。もしプラグインが存在しなければ Parent の hello() メソッドを呼び出します。ただ、本来は親から各プラグインのメソッドを呼び出すべきなので、このやり方だと無限ループになりそう・・・

TODO:

  • methodの有無をチェック
  • 有効にするプラグインを選べるようにする
  • Parentからプラグインを呼び出せるようにする
import importlib
import os

class Plugin:
    """
    Dynamically import Python scripts (*.py) in plugins directory.
    """
    PLUGINS_DIR = 'plugins'


    def __init__(self, base:type):
        self.base_class:type = base
        self.base_class_name:str = base.__name__
        self.plugins:list = []
        self.objs:list = []


    def plugin_names(self):
        """
        Return list of module names in the plugins directory
        """
        file:str
        module_names:list = []

        for file in os.listdir(self.PLUGINS_DIR):
            if file.endswith('.py') and file != '__init__.py':
                module_names.append(self.PLUGINS_DIR + '.' + os.path.splitext(file)[0])

        return module_names


    def import_plugins(self):
        """
        Import modules in the plugins directory
        """
        module_name:str

        for module_name in self.plugin_names():
            module = importlib.import_module(module_name)
            self.plugins += getattr(module, self.base_class_name).__subclasses__()


    def init_objs(self):
        """
        Create class instances and return as a list of objects
        """
        if len(self.objs) > 0:
            return self.objs

        if len(self.plugins) == 0:
            self.objs = [self.base_class()]
        else:
            for p in self.plugins:
                self.objs.append(p())

        return self.objs



    def call_method(self, method_name:str):
        """
        Call method of plugins (or base class if no plugin loaded)
        TODO: Give arguments for the method
        """
        for obj in self.init_objs():
            method = getattr(obj, method_name)
            method()


if __name__ == '__main__':
    from parent import Parent
    parent_plugin = Plugin(Parent)
    parent_plugin.import_plugins()
    parent_plugin.call_method('hello')

Discussion