Архитектура плагина – Plugin Manager vs проверка из импорта плагинов *

В настоящее время я пишу приложение, которое позволяет пользователю расширять его с помощью архитектуры типа «плагин». Они могут писать дополнительные классы python на основе объекта BaseClass, который я предоставляю, и они загружаются с различными сигналами приложения. Точное число и имена классов, загружаемых как плагины, неизвестны до запуска приложения, но загружаются только один раз при запуске.

Во время моего исследования наилучшего способа решения этой проблемы я придумал два общих решения.

Вариант 1 – сворачивайте свой собственный с помощью imp, pkgutil и т. Д.
См. Например, этот ответ или этот .

Вариант 2. Использование библиотеки диспетчера подключаемых модулей
Случайно подбирая пару

  • straight.plugin
  • yapsy
  • этот подход

Мой вопрос: при условии, что приложение должно быть перезапущено для загрузки новых плагинов – есть ли какая-либо польза от вышеуказанных методов над чем-то, вдохновленным этим 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 для предоставления сигналов, на которые они подписываются. Каждый плагин может подписаться на разные сигналы разных типов и, следовательно, должен иметь свой собственный конкретный метод «регистрации».

    3 Solutions collect form web for “Архитектура плагина – Plugin Manager vs проверка из импорта плагинов *”

    Подход метакласса полезен для этой проблемы в 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() 

    См. Полный рабочий пример

    Поскольку Python 3.6 добавлен новый метод класса __init_subclass__ , который вызывается в базовом классе, всякий раз, когда создается новый подкласс.

    Этот метод может дополнительно упростить решение, предлагаемое волейболом, путем удаления метакласса.

    Метод __init_subclass__ был введен с помощью PEP 487: Упрощенная настройка создания класса . PEP поставляется с минимальным примером для архитектуры плагина:

    Теперь можно настроить создание подкласса без использования метакласса. Новый __init_subclass__ __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 

    Подход от воли был самым полезным для меня! Поскольку я нуждался в большем контроле, я завернул класс 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() 

    Это позволяет добавить дополнительные базовые очки или переключиться на другой метакласс.

      Python - лучший язык программирования в мире.