Создание минимальной архитектуры плагинов в Python - PullRequest
176 голосов
/ 31 мая 2009

У меня есть приложение, написанное на Python, которое используется довольно технической аудиторией (учеными).

Я ищу хороший способ сделать приложение расширяемым для пользователей, то есть архитектуру сценариев / плагинов.

Я ищу что-то чрезвычайно легкий . Большинство сценариев, или плагинов, не будут разрабатываться и распространяться сторонними разработчиками и устанавливаться, но через несколько минут пользователь собирается что-то сделать, чтобы автоматизировать повторяющуюся задачу, добавить поддержку формата файла, и т. д. Таким образом, плагины должны иметь абсолютный минимальный шаблонный код и не требовать «установки», кроме копирования в папку (так что что-то вроде точек входа setuptools или архитектуры плагинов Zope кажется слишком большим).

Существуют ли какие-либо системы, подобные этой, или какие-либо проекты, в которых реализована аналогичная схема, на которую я должен обратить внимание для идей / вдохновения?

Ответы [ 18 ]

144 голосов
/ 31 мая 2009

Мой, в основном, каталог, называемый «плагинами», который основное приложение может опрашивать, а затем использовать imp.load_module для поиска файлов, искать известную точку входа, возможно, с уровнем модуля параметры конфигурации, и идти оттуда. Я использую материал для мониторинга файлов для определенного динамизма, при котором плагины активны, но это приятно иметь.

Конечно, любое требование, которое приходит со словами: «Мне не нужен [большой, сложный предмет] X; я просто хочу что-нибудь легковесное», рискует повторно реализовать X по одному обнаруженному требованию за раз. Но это не значит, что вы все равно не можете повеселиться, делая это :))

48 голосов
/ 01 июня 2009

module_example.py:

def plugin_main(*args, **kwargs):
    print args, kwargs

loader.py:

def load_plugin(name):
    mod = __import__("module_%s" % name)
    return mod

def call_plugin(name, *args, **kwargs):
    plugin = load_plugin(name)
    plugin.plugin_main(*args, **kwargs)

call_plugin("example", 1234)

Это, безусловно, «минимальный», в нем абсолютно нет проверки ошибок, возможно, множество проблем с безопасностью, он не очень гибкий - но он должен показать вам, насколько простой может быть система плагинов в Python.

Возможно, вы тоже захотите заглянуть в модуль imp , хотя вы можете многое сделать с помощью __import__, os.listdir и некоторых манипуляций со строками.

30 голосов
/ 31 мая 2009

Посмотрите на этот обзор существующих платформ / библиотек плагинов , это хорошая отправная точка. Мне очень нравится япси , но это зависит от вашего варианта использования.

25 голосов
/ 31 мая 2009

Хотя этот вопрос действительно интересный, я думаю, что на него довольно сложно ответить без подробностей. Что это за приложение? У него есть графический интерфейс? Это инструмент командной строки? Набор скриптов? Программа с уникальной точкой входа и т.д ...

Учитывая небольшую информацию, которую я имею, я отвечу очень обобщенно.

Что значит добавить плагины?

  • Возможно, вам придется добавить файл конфигурации, в котором будут перечислены пути / каталоги для загрузки.
  • Другим способом было бы сказать, что «любые файлы в этом каталоге плагинов / будут загружены», но неудобно требовать от пользователей перемещения по файлам.
  • Последний, промежуточный вариант - требовать, чтобы все плагины находились в одной плагине / папке, а затем активировать / деактивировать их, используя относительные пути в файле конфигурации.

На основе чистого кода / практики проектирования вам необходимо четко определить, какое поведение / конкретные действия вы хотите расширить для своих пользователей. Определите общую точку входа / набор функций, которые всегда будут переопределены, и определите группы в рамках этих действий. Как только это будет сделано, ваше приложение будет легко расширять,

Пример использования хуков , вдохновленный MediaWiki (PHP, но действительно ли язык имеет значение?):

import hooks

# In your core code, on key points, you allow user to run actions:
def compute(...):
    try:
        hooks.runHook(hooks.registered.beforeCompute)
    except hooks.hookException:
        print('Error while executing plugin')

    # [compute main code] ...

    try:
        hooks.runHook(hooks.registered.afterCompute)
    except hooks.hookException:
        print('Error while executing plugin')

# The idea is to insert possibilities for users to extend the behavior 
# where it matters.
# If you need to, pass context parameters to runHook. Remember that
# runHook can be defined as a runHook(*args, **kwargs) function, not
# requiring you to define a common interface for *all* hooks. Quite flexible :)

# --------------------

# And in the plugin code:
# [...] plugin magic
def doStuff():
    # ....
# and register the functionalities in hooks

# doStuff will be called at the end of each core.compute() call
hooks.registered.afterCompute.append(doStuff)

Еще один пример, вдохновленный ртутью. Здесь расширения только добавляют команды в исполняемый файл командной строки hg , расширяя поведение.

def doStuff(ui, repo, *args, **kwargs):
    # when called, a extension function always receives:
    # * an ui object (user interface, prints, warnings, etc)
    # * a repository object (main object from which most operations are doable)
    # * command-line arguments that were not used by the core program

    doMoreMagicStuff()
    obj = maybeCreateSomeObjects()

# each extension defines a commands dictionary in the main extension file
commands = { 'newcommand': doStuff }

Для обоих подходов может потребоваться обычная инициализация и финализация для вашего расширения. Вы можете либо использовать общий интерфейс, который будет реализовывать все ваше расширение (лучше подходит для второго подхода; Mercurial использует reposetup (ui, repo), который вызывается для всех расширений), либо использовать подход типа ловушки с hooks.setup hook.

Но опять же, если вам нужны более полезные ответы, вам придется сузить свой вопрос;)

11 голосов
/ 31 мая 2009

Я - биолог в отставке, который имел дело с цифровыми микрографами и обнаружил, что ему нужно написать пакет обработки и анализа изображений (технически это не библиотека) для работы на машине SGi. Я написал код на C и использовал Tcl для языка сценариев. GUI, каким бы он ни был, был сделан с использованием Tk. Команды, которые появились в Tcl, имели форму «extensionName commandName arg0 arg1 ... param0 param1 ...», то есть простые разделенные пробелом слова и числа. Когда Tcl увидел подстроку extensionName, управление было передано пакету C. Это, в свою очередь, запускало команду через лексер / парсер (сделанный в lex / yacc) и затем вызывало подпрограммы C по мере необходимости.

Команды для работы с пакетом можно запускать одну за другой через окно в графическом интерфейсе, но пакетные задания выполнялись путем редактирования текстовых файлов, которые были действительными сценариями Tcl; Вы выбрали шаблон, который выполнял ту операцию на уровне файлов, которую вы хотели выполнить, и затем отредактировали копию, чтобы в ней содержались фактические имена каталогов и файлов, а также команды пакета. Оно работало завораживающе. До ...

1) Мир превратился в ПК и 2) скрипты стали длиннее, чем около 500 строк, когда громоздкие организационные возможности Tcl начали становиться настоящим неудобством. Время прошло ...

Я удалился, Python был изобретен, и он выглядел как идеальный преемник Tcl. Теперь я никогда не занимался портом, потому что никогда не сталкивался с проблемами компиляции (довольно больших) программ на C на ПК, расширения Python с помощью пакета C и создания GUI на Python / Gt? / Tk? /? ?. Тем не менее, старая идея наличия редактируемых шаблонных скриптов кажется все еще работоспособной. Кроме того, ввод команд пакета в нативной форме Python не должен быть слишком тяжелым, например:

packageName.command (arg0, arg1, ..., param0, param1, ...)

Несколько лишних точек, скобок и запятых, но это не шоу-стопперы.

Я помню, что кто-то делал версии lex и yacc в Python (попробуйте: http://www.dabeaz.com/ply/),, поэтому, если они все еще нужны, они есть.

Смысл этого бессвязного в том, что мне показалось, что сам Python - это желаемый «легкий» интерфейс, используемый учеными. Мне любопытно узнать, почему вы думаете, что это не так, и я имею в виду это серьезно.


добавлено позже: Приложение gedit ожидает добавления плагинов, и на их сайте есть самое ясное объяснение простой процедуры плагинов, которую я нашел за несколько минут осмотра. Попробуйте:

https://wiki.gnome.org/Apps/Gedit/PythonPluginHowToOld

Я все еще хотел бы лучше понять ваш вопрос. Мне неясно, хотите ли вы, 1), чтобы ученые могли использовать ваше (Python) приложение довольно просто различными способами, или 2) хотите, чтобы ученые добавили новые возможности в ваше приложение. Выбор № 1 - это ситуация, с которой мы столкнулись с изображениями, и которая заставила нас использовать общие сценарии, которые мы изменили в соответствии с потребностями момента. Это вариант № 2, который приводит вас к идее плагинов, или это какой-то аспект вашего приложения, который делает невыполнимым ввод команд для него?

11 голосов
/ 14 марта 2010

Простая платформа плагинов Марти Аллчина - это база, которую я использую для своих собственных нужд. Я действительно рекомендую взглянуть на это, я думаю, что это действительно хорошее начало, если вы хотите что-то простое и легко взломанное. Вы также можете найти его как Django Snippets .

10 голосов
/ 28 октября 2013

Когда я искал Python Decorators, нашел простой, но полезный фрагмент кода. Он может не соответствовать вашим потребностям, но очень вдохновляет.

Система регистрации плагинов Scipy Advanced Python #

class TextProcessor(object):
    PLUGINS = []

    def process(self, text, plugins=()):
        if plugins is ():
            for plugin in self.PLUGINS:
                text = plugin().process(text)
        else:
            for plugin in plugins:
                text = plugin().process(text)
        return text

    @classmethod
    def plugin(cls, plugin):
        cls.PLUGINS.append(plugin)
        return plugin


@TextProcessor.plugin
class CleanMarkdownBolds(object):
    def process(self, text):
        return text.replace('**', '')

Использование:

processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")
7 голосов
/ 01 июня 2009

Мне понравилось хорошее обсуждение различных архитектур плагинов, которое д-р Андре Роберге дал на Pycon 2009. Он дает хороший обзор различных способов реализации плагинов, начиная с чего-то действительно простого.

Доступен в виде подкаста (вторая часть после объяснения исправлений обезьян), сопровождаемого серией шести записей в блоге .

Я рекомендую внимательно выслушать его, прежде чем принять решение.

4 голосов
/ 26 августа 2015

На самом деле setuptools работает с «каталогом плагинов», как показано в следующем примере из документации проекта: http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

Пример использования:

plugin_dirs = ['foo/plugins'] + sys.path
env = Environment(plugin_dirs)
distributions, errors = working_set.find_plugins(env)
map(working_set.add, distributions)  # add plugins+libs to sys.path
print("Couldn't load plugins due to: %s" % errors)

В долгосрочной перспективе setuptools - гораздо более безопасный выбор, поскольку он может загружать плагины без конфликтов или пропущенных требований.

Еще одним преимуществом является то, что сами плагины могут быть расширены с использованием того же механизма, без необходимости в исходных приложениях.

4 голосов
/ 24 октября 2013

Я приехал сюда в поисках минимальной архитектуры плагинов и обнаружил множество вещей, которые мне показались излишними. Итак, я реализовал Super Simple Python Plugins . Чтобы использовать его, вы создаете один или несколько каталогов и добавляете специальный файл __init__.py в каждый. При импорте этих каталогов все остальные файлы Python будут загружены как подмодули, а их имена будут помещены в список __all__. Тогда вам остается проверить / инициализировать / зарегистрировать эти модули. В файле README есть пример.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...