Поддержка плагинов в программах на Python
Автор: Вадим Хохлов
В настоящее время множество программ поддерживают работу с плагинами (дополнительными модулями). Такая поддержка позволяет делать программы более гибкими и расширяемыми. В качестве примера можно привести известный медиапроигрыватель XMMS.
Существует библиотека dl, которая используется при разработке программ, поддерживающих механизм плагинов. О ее применении можно узнать из статьи "Добавление модулей расширения (плагинов) к программе", автор Tom Bradley.
В данной статье будет рассмотрен метод работы с плагинами в программах на Python.
Язык Python имеет встроенную поддержку модулей. В стандартную библиотеку Python'а входит множество модулей, содержащих функции для работы с операционной системой, протоколами и форматами, активно используемыми в сети Интернет, строками, базами данных и т.п.
Для подключения модулей в программу (которая тоже не что иное как модуль) используется инструкция import. Например, следующая строка загружает модуль fibo, который находится в файле fibo.py (пример взят из книги "Язык программирования Python"):
import fibo
При импорте модулей интерпретатор ищет файл с соответствующим именем в текущем каталоге, затем в каталогах, которые указанны в переменной PYTHONPATH, затем в путях по умолчанию (переменная окружения PATH).
Большинство программ на Python начинаются со строк, в которых с помощью данной инструкции и ее вариантов загружаются необходимые модули.
Имеется также возможность определить имя модуля динамически во время работы программы и загрузить его с помощью встроенной функции __import__.
Пусть необходимо разработать программу, которая в зависимости от подключенного плагина показывает в главном окне различные органы управления:
Плагин должен выбираться с помощью выпадающего списка. При нажатии на кнопку "get info" на экран будет выводиться некоторая информация о состоянии текущего плагина.
Сначала напишем модули плагинов. Будем использовать PyQt для создания интерфейсных элементов. Ниже представлен модуль, создающий окно с переключателем checkbox и меткой:
#! /usr/bin/python import sys, os import qt def getPluginWidget(parent): return PluginWidgetCheck(parent) class PluginWidgetCheck(qt.QWidget): def __init__(self, parent = None): qt.QWidget.__init__(self, parent, "ConfigWidget") topLayout = qt.QVBoxLayout(self, 0, 0, "topLayout") self.lb = qt.QLabel("label", self) topLayout.addWidget(self.lb) self.rb = qt.QCheckBox("test", self) topLayout.addWidget(self.rb) self.connect(self.rb, qt.SIGNAL("clicked()"), self.slotClick) def slotClick(self): if (self.rb.isChecked()): self.lb.setText("checked") else: self.lb.setText("unchecked") def getInfo(self): if (self.rb.isChecked()): return "checkbox is checked" else: return "checkbox is unchecked" if __name__ == "__main__": app = qt.QApplication(sys.argv) pluginWidget = PluginWidgetCheck() app.setMainWidget(pluginWidget) pluginWidget.show() app.exec_loop() |
Вот модуль с окном, которое содержит однострочный редактор и переключатель radiobutton:
#! /usr/bin/python import sys, os import qt def getPluginWidget(parent): return PluginWidgetRadio(parent) class PluginWidgetRadio(qt.QWidget): def __init__(self, parent = None): qt.QWidget.__init__(self, parent, "ConfigWidget") topLayout = qt.QVBoxLayout(self, 0, 0, "topLayout") self.rb = qt.QRadioButton("test", self) topLayout.addWidget(self.rb) self.le = qt.QLineEdit(self) topLayout.addWidget(self.le) self.connect(self.rb, qt.SIGNAL("clicked()"), self.slotRadio) def slotRadio(self): if (self.rb.isChecked()): self.le.setText("checked") else: self.le.setText("unchecked") def getInfo(self): if (self.rb.isChecked()): return "radiobutton is checked" else: return "radiobutton is unchecked" if __name__ == "__main__": app = qt.QApplication(sys.argv) pluginWidget = PluginWidgetRadio() app.setMainWidget(pluginWidget) pluginWidget.show() app.exec_loop() |
Оба модуля содержат функцию getPluginWidget, возвращающую виджет, который будет размещен в окне. У данного виджета есть метод getInfo, возвращающий информацию о его состоянии.
Обратите внимание на следующий фрагмент:
if __name__ == "__main__": app = qt.QApplication(sys.argv) pluginWidget = PluginWidgetRadio() app.setMainWidget(pluginWidget) pluginWidget.show() app.exec_loop() |
Переменная __name__ содержит имя модуля. Если же модуль запускается из командной строки, а не импортируется, эта переменная имеет значение __main__. Используя подобный фрагмент, можно выполнить отладку модуля отдельно и только потом его импортировать в программу.
Теперь рассмотрим текст программы:
#! /usr/bin/python import sys, os import qt class MainWidget(qt.QWidget): def __init__(self, parent = None): qt.QWidget.__init__(self, parent, "ConfigWidget") topLayout = qt.QVBoxLayout(self, 0, 0, "topLayout") self.cb = qt.QComboBox(0, self) topLayout.addWidget(self.cb) self.cb.insertItem("radio") self.cb.insertItem("check") self.gb = qt.QVGroupBox("conf", self) topLayout.addWidget(self.gb) self.pb = qt.QPushButton("get info", self) topLayout.addWidget(self.pb) self.toolWin = None self.connect(self.cb, qt.SIGNAL("activated(const QString&)"), self.slotToolChanged) self.connect(self.pb, qt.SIGNAL("clicked()"), self.slotGetInfo) self.slotToolChanged("radio") def slotToolChanged(self, toolName): toolName = str(toolName) if (sys.modules.has_key(toolName)): module = reload(sys.modules[toolName]) else: module = __import__(toolName, globals()) if(self.toolWin is not None): self.toolWin.close() self.toolWin = module.getPluginWidget(self.gb) self.toolWin.show() def slotGetInfo(self): print "info:" + self.toolWin.getInfo() app = qt.QApplication(sys.argv) mainWidget = MainWidget() app.setMainWidget(mainWidget) mainWidget.show() app.exec_loop() |
Загрузка плагинов выполняется в методе slotToolChanged. Сначала мы проверяем, загружался ли модуль ранее. Если да, то заново его считываем и инициализируем с помощью функции reload. Иначе модуль импортируется функцией __import__. После загрузки модуля внутри groupbox'а создается окно плагина.
Так как эта программа создавалась лишь с демонстрационными целями, я намеренно не добавлял обработку ошибок.
Таким образом, встроенные возможности Python'а позволяют довольно легко разрабатывать программы, поддерживающие механизм плагинов.
Я работаю программистом и преподаю в Херсонском государственном техническом университете. С Linux знаком с 1999 года. Общаюсь с ним, в основном, дома. Кроме этого, я являюсь разработчиком IceWM Control Center - набора программ (в том числе и скриптов icerrun) для настройки различных параметров IceWM.
Мои хобби - игра в Что?Где?Когда?, аквариум, коты.