【问题标题】:Plugin architecture - Plugin Manager vs inspecting from plugins import *插件架构 - 插件管理器与从插件导入检查 *
【发布时间】:2013-01-24 20:38:37
【问题描述】:

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

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

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

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

我的问题是 - 前提是必须重新启动应用程序才能加载新插件 - 上述方法是否比受 this SO answerthis one 启发的东西有任何好处,例如:

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

【问题讨论】:

  • 如果您使用的是this approach,则不需要load_pluginspredicate 函数。这是我建议的。
  • 谢谢 - 是的,我确实看到了那篇文章......这是一个很好的方法。但是,由于每个插件都有不同的注册功能,我大概需要遍历插件并分别调用注册?这似乎比上面的要复杂得多?
  • 你需要多解释一下你到底想要什么。如果您使用文章中的想法,您的Base 类将有一个包含所有Plugins 注册的类属性。

标签: python plugins


【解决方案1】:

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

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

__init_subclass__ 方法是用PEP 487: Simpler customization of class creation 引入的。 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

【讨论】:

  • 这不就是重新实现__subclasses__吗?还有为什么还没有人提到ABC 模块
【解决方案2】:

metaclass approach 在 Python

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 2011-07-26
      • 1970-01-01
      • 2011-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多