插件架构 - 插件管理器与从插件导入*的比较

9 投票
3 回答
6860 浏览
提问于 2025-04-17 13:34

我现在正在写一个应用程序,允许用户通过一种“插件”架构来扩展它。用户可以根据我提供的一个基础类(BaseClass)编写额外的Python类,这些类会在应用程序的不同信号下被加载。具体会加载多少个插件类以及它们的名字在应用程序启动之前是未知的,但这些插件只会在启动时加载一次。

在研究如何最佳解决这个问题时,我想到了两种常见的方案。

方案一 - 自己动手使用 imp、pkgutil 等
比如,可以参考这个回答或者这个回答

方案二 - 使用插件管理库
随便挑选几个:

我的问题是 - 在应用程序必须重启才能加载新插件的前提下,上述方法相比于这个回答这个回答,有什么好处呢?例如:

import inspect
import sys
import my_plugins

def predicate(c):
    # filter to classes
    return inspect.isclass(c)

def load_plugins():
    for name, obj in inspect.getmembers(sys.modules['my_plugins'], predicate):
        obj.register_signals()

这种方法相比于上面的方案有什么缺点吗?(除了所有插件必须在同一个文件中)谢谢!

编辑
评论中请求更多信息……我能想到的唯一补充是,这些插件使用blinker库来提供它们所订阅的信号。每个插件可能会订阅不同类型的不同信号,因此必须有自己特定的“注册”方法。

3 个回答

0

will-hart 的方法对我来说最有帮助了!

因为我需要更多的控制,所以我把插件的基础类放在了一个函数里,像这样:

def get_plugin_base(name='Plugin',
                       cls=object,
                       metaclass=PluginMount):

    def iter_func(self):
        for mod in self._models:
            yield mod

    bases = not isinstance(cls, tuple) and (cls,) or cls

    class_dict = dict(
        _models=None,
        session=None
    )

    class_dict['__iter__'] = iter_func

    return metaclass(name, bases, class_dict)

然后:

from plugin import get_plugin_base
Plugin = get_plugin_base()

这样做可以让我添加更多的基础类,或者切换到其他的元类。

15

自从 Python 3.6 版本开始,新增了一个类方法 __init_subclass__。这个方法会在每次创建新子类时被调用,作用于基类。

这个方法可以让上面提到的 will-hart 的解决方案变得更简单,因为它不需要使用元类了。

__init_subclass__ 方法是通过 PEP 487: 更简单的类创建定制 引入的。这个提案中有一个关于插件架构的简单示例:

现在可以在不使用元类的情况下定制子类的创建。每当创建新子类时,新的 __init_subclass__ 类方法会在基类上被调用:

class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

上面的 PEP 示例中,将类的引用存储在 Plugin.plugins 字段里。

如果你想存储插件类的实例,可以使用这样的结构:

class Plugin:
    """Base class for all plugins. Singleton instances of subclasses are created automatically and stored in Plugin.plugins class field."""
    plugins = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.plugins.append(cls())

class MyPlugin1(Plugin):
    def __init__(self):
        print("MyPlugin1 instance created")

    def do_work(self):
        print("Do something")

class MyPlugin2(Plugin):
    def __init__(self):
        print("MyPlugin2 instance created")

    def do_work(self):
        print("Do something else")

for plugin in Plugin.plugins:
    plugin.do_work()

这样会输出:

MyPlugin1 instance created
MyPlugin2 instance created
Do something
Do something else
7

在Python 3.6之前,使用元类的方法对解决这个问题很有帮助(如果你用的是Python 3.6及以上版本,可以看看@quasoft的回答)。这个方法非常简单,并且会自动作用于任何被导入的模块。此外,它还可以很轻松地对插件注册进行复杂的逻辑处理。这个方法需要:

元类的方法大致是这样的:

1) 定义一个自定义的 PluginMount 元类,这个元类会维护一个所有插件的列表。

2) 定义一个 Plugin 类,并将 PluginMount 设置为它的元类。

3) 当导入一个从 Plugin 派生的对象,比如 MyPlugin 时,会触发元类的 __init__ 方法。这个过程会注册插件,并执行任何特定于应用程序的逻辑和事件订阅。

另外,如果你把 PluginMount.__init__ 的逻辑放在 PluginMount.__new__ 中,那么每当创建一个 Plugin 派生类的新实例时,这个逻辑就会被调用。

class PluginMount(type):
    """
    A plugin mount point derived from:
        http://martyalchin.com/2008/jan/10/simple-plugin-framework/
    Acts as a metaclass which creates anything inheriting from Plugin
    """

    def __init__(cls, name, bases, attrs):
        """Called when a Plugin derived class is imported"""

        if not hasattr(cls, 'plugins'):
            # Called when the metaclass is first instantiated
            cls.plugins = []
        else:
            # Called when a plugin class is imported
            cls.register_plugin(cls)

    def register_plugin(cls, plugin):
        """Add the plugin to the plugin list and perform any registration logic"""

        # create a plugin instance and store it
        # optionally you could just store the plugin class and lazily instantiate
        instance = plugin()

        # save the plugin reference
        cls.plugins.append(instance)

        # apply plugin logic - in this case connect the plugin to blinker signals
        # this must be defined in the derived class
        instance.register_signals()

然后,一个基础的插件类看起来像这样:

class Plugin(object):
    """A plugin which must provide a register_signals() method"""
    __metaclass__ = PluginMount

最后,一个实际的插件类会像下面这样:

class MyPlugin(Plugin):
    def register_signals(self):
        print "Class created and registering signals"

    def other_plugin_stuff(self):
        print "I can do other plugin stuff"

任何导入了 Plugin 的Python模块都可以访问这些插件:

for plugin in Plugin.plugins:
    plugin.other_plugin_stuff()

可以查看完整的工作示例

撰写回答