插件架构 - 插件管理器与插件导入检查*

wil*_*art 6 python plugins

我目前正在编写一个应用程序,允许用户通过"插件"类型架构扩展它.他们可以根据我提供的BaseClass对象编写其他python类,并根据各种应用程序信号加载这些类.在启动应用程序之前,作为插件加载的类的确切数量和名称是未知的,但仅在启动时加载一次.

在我研究解决这个问题的最佳方法的过程中,我提出了两个常见的解决方案.

选项1 - 使用imp,pkgutil等滚动自己.
例如,请参阅此答案答案.

选项2 - 使用插件管理器库
随机选择一对

我的问题是-在应用程序必须以加载新的插件重新启动条件-是否有过什么灵感来自上述方法的任何利益这个苏答案这一个,如:

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()
Run Code Online (Sandbox Code Playgroud)

与上述方法相比,这种方法有任何缺点吗?(除了所有插件必须在同一个文件中)谢谢!

编辑
评论请求进一步的信息...我能想到的唯一额外的事情是插件使用闪烁库来提供他们订阅的信号.每个插件可以订阅不同类型的不同信号,因此必须具有其自己的特定"寄存器"方法.

wil*_*art 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()
Run Code Online (Sandbox Code Playgroud)

然后是一个基本插件类,看起来像:

class Plugin(object):
    """A plugin which must provide a register_signals() method"""
    __metaclass__ = PluginMount
Run Code Online (Sandbox Code Playgroud)

最后,实际的插件类如下所示:

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"
Run Code Online (Sandbox Code Playgroud)

可以从导入的任何python模块访问插件Plugin:

for plugin in Plugin.plugins:
    plugin.other_plugin_stuff()
Run Code Online (Sandbox Code Playgroud)

查看完整的工作示例


qua*_*oft 7

Python 3.6开始,__init_subclass__添加了一个新的类方法,只要创建了一个新的子类,就会在基类上调用它.

通过删除元类,该方法可以进一步简化上面将要提供的解决方案.

__init_subclass__方法是在PEP 487中引入的:更简单的类创建定制.PEP附带了一个插件架构的最小示例:

现在可以在不使用元类的情况下自定义子类创建.__init_subclass__每当创建新的子类时,都会在基类上调用新的classmethod:

class PluginBase:
    subclasses = []

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

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass
Run Code Online (Sandbox Code Playgroud)

上面的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()
Run Code Online (Sandbox Code Playgroud)

哪个输出:

MyPlugin1 instance created
MyPlugin2 instance created
Do something
Do something else
Run Code Online (Sandbox Code Playgroud)