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

时间:2013-01-24 20:38:37

标签: python plugins

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

在研究解决这个问题的最佳方法时,我提出了两个常见的解决方案。

选项1 - 使用imp,pkgutil等滚动自己
例如,请参阅this answerthis one

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

我的问题是 - 条件是必须重新启动应用程序以加载新插件 - 上述方法的任何好处都取决于this SO answerthis one的启发,例如:< / p>

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 :(得分:7)

metaclass approach对于Python中的这个问题很有用。 3.6(参见@ quasoft对Python 3.6+的回答)。它非常简单,可以在任何导入的模块上自动执行。此外,复杂的逻辑可以很少的努力应用于插件注册。它要求:

metaclass方法的工作原理如下:

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

请参阅the full working example

答案 1 :(得分:6)

Python 3.6以来,添加了一个新的类方法__init_subclass__,无论何时创建新的子类,都会在基类上调用它。

通过删除元类,此方法可以进一步简化上述will-hart提供的解决方案。

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

  

现在可以在不使用a的情况下自定义子类创建   元类。将调用新的__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

答案 2 :(得分:0)

来自will-hart的方法对我来说是最有用的方法! 因为我需要更多控制,我将Plugin Base类包装在一个函数中:

Password:="____"

然后:

PDW=____

这允许添加其他基类或切换到另一个元类。