插件架构 - 插件管理器 vs 从插件导入*进行检查

9
我目前正在编写一款应用程序,允许用户通过“插件”类型的架构来扩展它。他们可以根据我提供的BaseClass对象编写额外的Python类,并针对各种应用程序信号进行加载。在启动应用程序之前,无法确定作为插件加载的类的确切数量和名称,但仅在启动时加载一次。
在研究如何解决这个问题的最佳方法时,我想到了两个常见的解决方案。
选项1-使用imp,pkgutil等自行开发。
例如,请参见此答案此答案
选项2-使用插件管理器库
随机选择几个 我的问题是-假设必须重新启动应用程序才能加载新插件-上述方法是否比受启发于此SO答案这个更具优势:
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库来提供它们订阅的信号。每个插件可能订阅不同类型的不同信号,因此必须具有自己特定的“注册”方法。

如果您正在使用这种方法,那么您就不需要load_pluginspredicate函数。这是我建议的方法。 - Wessie
谢谢 - 是的,我看到了那篇文章...这是一个好方法。然而,由于每个插件都有不同的注册函数,我可能仍需要循环遍历插件并单独调用注册?这似乎比上面的方法要复杂得多? - will-hart
你需要更详细地解释你想要的是什么。如果您使用文章中的想法,您的“Base”类将具有包含所有注册的“Plugins”的类属性。 - Wessie
3个回答

14

自从 Python 3.6 推出新的类方法 __init_subclass__,只要创建一个新的子类,就会在基类上调用该方法。

这个方法可以进一步简化 will-hart 上面提供的解决方案,无需使用元类。

PEP 487: Simpler customization of class creation 引入了 __init_subclass__ 方法。该 PEP 提供了一个插件架构的最小示例:

It is now possible to customize subclass creation without using a metaclass. The new __init_subclass__ classmethod will be called on the base class whenever a new subclass is created:

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

1
这不就是重新实现__subclasses__吗?另外,为什么没有人提到ABC模块呢? - Xaser

7

元类方法 在 Python < 3.6 中对此问题很有用(请参见 @quasoft 的 Python 3.6+ 答案)。 它非常简单,可以自动地作用于任何导入的模块。 此外,可以轻松应用复杂逻辑来进行插件注册。 它需要:

元类 方法的工作方式如下:

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()

查看完整的工作示例


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()

这允许添加额外的基类或切换到另一个元类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接