2013-01-24 1 views
4

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

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

Вариант 1 - ролл свой собственный, используя имп, pkgutil и т.д.
смотри, например, this answer или this one.

Вариант 2 - Использование библиотеки менеджер плагин
Случайным собирание пару

Мой вопрос - на том условии, что заявление должно быть рестартом Ted, чтобы загрузить новые плагины - есть ли польза от вышеперечисленных методов над чем-то вдохновляли от this SO answer и this 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() 

Существуют ли какие-либо недостатки этого подхода по сравнению с представленным выше? (кроме всех плагинов должны быть в одном файле) Спасибо!

EDIT
Комментарии запросить дополнительную информацию ... только дополнительная вещь, которую я могу думать, чтобы добавить, что модули используют blinker библиотеку, чтобы обеспечить сигналы, которые они подписываются. Каждый плагин может подписаться на разные сигналы разных типов и, следовательно, должен иметь свой собственный конкретный метод «регистрации».

+0

Если вы используете [этот подход] (HTTP : //martyalchin.com/2008/jan/10/simple-plugin-framework/), то вам не нужны функции load_plugins и предикат. И я предлагаю. – Wessie

+0

Спасибо - да, я видел эту статью ... это хороший подход. Однако, поскольку каждый плагин будет иметь другую регистрационную функцию, мне, по-видимому, нужно будет все равно перебирать плагины и регистрировать регистр отдельно? Это кажется значительно более сложным, чем выше? –

+0

Вам нужно объяснить немного больше того, чего вы точно хотите. Если вы используете идею из статьи, ваш класс «Base» будет иметь атрибут класса, который содержит все зарегистрированные «Plugins». – Wessie

ответ

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:

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

См. the full working example

0

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

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

4

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

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

Метод __init_subclass__ был введен с PEP 487: Simpler customization of class creation. 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 

пример 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 
Смежные вопросы