Поддержка плагинов в программах на Python

Постановка задачи: пусть необходимо разработать программу (на языке Python), которая в зависимости от подключенного плагина показывает в главном окне различные органы управления. Плагин должен выбираться с помощью выпадающего списка. При нажатии на кнопку "get info" на экран будет выводиться некоторая информация о состоянии текущего плагина.

[Вадим Хохлов]

Поддержка плагинов в программах на Python

Автор: Вадим Хохлов

Введение

В настоящее время множество программ поддерживают работу с плагинами (дополнительными модулями). Такая поддержка позволяет делать программы более гибкими и расширяемыми. В качестве примера можно привести известный медиапроигрыватель XMMS.

Существует библиотека dl, которая используется при разработке программ, поддерживающих механизм плагинов. О ее применении можно узнать из статьи "Добавление модулей расширения (плагинов) к программе", автор Tom Bradley.

В данной статье будет рассмотрен метод работы с плагинами в программах на Python.

Теория

Язык Python имеет встроенную поддержку модулей. В стандартную библиотеку Python'а входит множество модулей, содержащих функции для работы с операционной системой, протоколами и форматами, активно используемыми в сети Интернет, строками, базами данных и т.п.

Для подключения модулей в программу (которая тоже не что иное как модуль) используется инструкция import. Например, следующая строка загружает модуль fibo, который находится в файле fibo.py (пример взят из книги "Язык программирования Python"):

import fibo

При импорте модулей интерпретатор ищет файл с соответствующим именем в текущем каталоге, затем в каталогах, которые указанны в переменной PYTHONPATH, затем в путях по умолчанию (переменная окружения PATH).

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

Имеется также возможность определить имя модуля динамически во время работы программы и загрузить его с помощью встроенной функции __import__.

Постановка задачи

Пусть необходимо разработать программу, которая в зависимости от подключенного плагина показывает в главном окне различные органы управления:

checkbox's plugin radiobutton's plugin

Плагин должен выбираться с помощью выпадающего списка. При нажатии на кнопку "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.

Мои хобби - игра в Что?Где?Когда?, аквариум, коты.


Статью можно найти по адресу: http://gazette.linux.ru.net/rus/articles/pyplug.html

[ опубликовано 31/07/2004 ]

Вадим Хохлов - Поддержка плагинов в программах на Python   Версия для печати