7

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

在研究解决此问题的最佳方法期间,我提出了两种常见的解决方案。

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

选项 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库来提供他们订阅的信号。每个插件可以订阅不同类型的不同信号,因此必须有自己特定的“注册”方法。

4

3 回答 3

10

Python 3.6开始,添加了一个新的类方法__init_subclass__,每当创建新的子类时都会在基类上调用该方法。

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

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

现在可以在不使用元类的情况下自定义子类创建。__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
于 2017-01-29T18:19:08.463 回答
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"

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

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

查看完整的工作示例

于 2013-07-01T09:38:06.680 回答
0

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

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

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

于 2016-06-14T10:15:53.723 回答