« Поставить закладку » « Сделать стартовой »

« Форумы » « Блоги » « Статьи » « Новости » « Файлы » « Realcoding IRC » « Site map » « Поиск »


Главная Главная
Анонсы Анонсы
Форумы Форумы
Каталог Каталог
Поиск Поиск
Опросы Опросы
Книжный магазин Книжный магазин
Реклама на сайте
Публикации Публикации
Партнеры Партнеры
Карта Карта сайта
Рассылки Рассылки
RSS экспорт
Настройки Настройки
О нас пишут О нас пишут
Контакты Контакты
Гостевая книга Гостевая книга


ПнВтСрЧтПтСбВс
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
    Популярное
Функция lstrcmp

Функция CreateFontIndirect

Глава 5. ИСПОЛЬЗОВАНИЕ СПЕЦИАЛЬНЫХ ОПЕРАТОРОВ В УСЛОВИЯХ

Функция AccessResource

Разработка приложений на .NET Compact Framework 3.5 для Windows Mobile 6. Станислав Павлов. Quarta Technologies

Мероприятия: Сетевой стек нового поколения NG TCP/IP для Windows Vista и Windows Server 2008

Гостевая книга на ASP.NET

Простой пример работы с массивами в C#

Архитектура Web-службы

Создание меню




    Архив файлов



    Сообщества

    Документация

    Кто на сайте
Вы не зарегистрированы.
Имя:

Пароль:

Запомнить

Регистрация позволит Вам пользоваться дополнительными сервисами.
Сейчас на сайте:
Гостей: 107
Пользователей: 0

Статьи:: Интернет технологии :: Python :: Статьи :: Делаем плагины на Python



отправить ссылку другу версия для печати  Обсудить на форуме

Делаем плагины на Python

Автор: http://jenyay.net/ 

Введение

Многие программы поддерживают так называемые плагины (дополнение, расширения и т.п.), с помощью которых можно расширять функциональность программы. На Python делать программы, поддерживающие плагины особенно легко и приятно. Потому что с одной стороны с качестве плагина могут выступать полноценные классы, а с другой стороны благодаря кроссплатформенности языка Python плагины так же остаются кроссплатформенными. Давайте посмотрим что нужно сделать, чтобы ваша программа тоже поддерживала плагины. Заодно убедимся как это легко.



Первый пример. Основные функции

Вначале давайте договоримся о структуре тестовых примеров. Модуль основной программы будет называться main.py, а плагины будут лежать в папке plugins, располагающейся рядом с этим файлом. Чтобы Python принял plugins за пакет, в нем должен находиться файл с именем __init__.py (в нашем случае он будет пустым).

Сначала представим, что динамически во время выполнения программы нам не нужно узнавать имя плагина и мы его знаем на этапе программирования. Пусть плагин имеет имя my_plugin.py и располагается в папке plugins. И пусть внутри файла my_plugin.py находится класс pluginClass, который содержит всю функциональность плагина. Вот его код:

class pluginClass (object):
        def __init__(self):
                pass

        def run (self):
                print "Hello, Plug-in!"

В конечном итоге нам необходимо во время выполнения программы добраться до этого класса, создать его экземпляр и выполнить метод run. Для простоты на первое время договоримся, что в этом модуле нет других классов кроме pluginClass. Как бы мы поступили, если бы все имена (и модуля, и класса) были бы доступны во время программирования? Скорее всего вот так:

import plugins.my_plugin
cls = plugins.my_plugin.pluginClass ()
cls.run()

И в результате получили бы сообщение "Hello, Plug-in!". А теперь вернемся к нашей задаче. Нам нужно сделать то же самое, но при этом имя модуля my_plugin и имя класса pluginClass хранится в соответствующих строковых переменных.

Импорт модуля плагина

Аналогом встроенной директивы import является функция __import__ , она позволяет импортировать модули, имена которых на этапе написания программы неизвестны. У функции __import__ пять параметров, но обязательным является только первый. Необязательные параметры в данной статье мы использовать не будем, поэтому про них умолчим. Итак, единственный обязательный параметр - это имя пакета или модуля, который мы хотим импортировать. Если импорт пройдет удачно, функция возвратит экземпляр класса, который хранит все импортированные элементы.

Начнем с импортирования модуля. Директива import нам не поможет. Зато мы можем воспользоваться функцией __import__ . Аналогом первой строки из записанного выше примера будет следующий код:

package_obj = __import__("plugins.my_plugin")

После этого переменная package_obj станет экземпляром класса загруженного модуля (пакета) plugins. Чтобы убедиться в этом выполним команду

print package_obj

При этом мы получим что-то вроде (путь, разумеется, может быть другой):

<module 'plugins' from 'H:ProjectsTestsPyPluginstest1plugins__init__.pyc'>

Это сообщение мало информативно, поэтому применим к переменной package_obj встроенную функцию dir, которая возвращает имеющиеся в package_obj атрибуты. Итак, выполним следующий код:

print dir(package_obj)

В результате на экран выведется такой список:

['__builtins__', '__doc__', '__file__', '__name__', '__path__', 'my_plugin']

Обратите внимание на последний элемент списка - это и есть наш плагин. Итак, пакет мы загрузили, но как нам добраться до модуля нашего плагина? Для этого сначала воспользуемся встроенной функцией getattr, которая позволяет получить из модуля или пакета (в нашем случае package_obj) экземпляр класса атрибута (а нашем случае my_plugin). Эта функция принимает два параметра: соответственно экземпляр объекта, атрибут которого надо получить и строковую переменную, которая содержит имя атрибута. Применяя функцию getattr на пакет, в случае успеха мы получим экземпляр загруженного модуля. Выполним следующий код:

module_obj = getattr (package_obj, "my_plugin")
print module_obj

Если все прошло удачно, на экране мы увидим примерно такой результат:

<module 'plugins.my_plugin' from 'H:ProjectsTestsPyPluginstest1pluginsmy_plugin.pyc'>

Но скорее всего в программе придется загружать не один плагин, а несколько. Как в этом случае поведет себя функция __import__ ? Рассмотрим пример, в котором загружаются два плагина (все они должны находиться в папке plugins.

#-*- coding: utf-8 -*-

modulename1 = "my_plugin_1"
modulename2 = "my_plugin_2"
classname = "pluginClass1"

package_obj = __import__("plugins." + modulename1 )
package_obj = __import__("plugins." + modulename2 )

print dir(package_obj)

В результате на экране вы увидите следующий результат:

['__builtins__', '__doc__', '__file__', '__name__', '__path__', 'my_plugin_1', 'my_plugin_2']

В этом примере результат импорта мы каждый раз присваиваем одной и той же переменной. Но в результате после каждой операции импорта в нее добавляется новый импортированный модуль. Скачать этот пример можно по адресу - test1_1.zip.

Получаем доступ к классу

Итак, модуль my_plugin мы загрузили. Осталось добраться до класса, который содержится внутри. Для этого воспользуемся уже знакомой нам функцией dir и убедимся, внутри действительно хранится наш класс:

print dir (module_obj)

В результате выполнения этого кода получим:

['__builtins__', '__doc__', '__file__', '__name__', 'pluginClass']

Как видим, класс pluginClass действительно содержится внутри module_obj. Снова воспользуемся функцией getattr:

obj = getattr (module_obj, "pluginClass")

В принципе, уже после этого мы можем сделать экземпляр класса pluginClass, но для начала сделаем небольшую проверку того, что мы получили. Убедимся, что полученный объект действительно является классом, причем классом, вроизводным от object. Для этого воспользуемся встроенной функцией issubclass. Как ее использовать ясно из следующего кода:

if issubclass (obj, object):
        a = obj()
        a.run()
else:
        print "Not class"

Если все сделано правильно, то в результате мы увидим сообщение "Hello, Plug-in!"

И все вместе

Теперь для наглядности сведем вместе весь код, что мы раньше написали. В комментариях после операторов print приведено то, что они выведут на экран.

#-*- coding: utf-8 -*-

modulename = "my_plugin"
classname = "pluginClass"

package_obj = __import__("plugins." + modulename )

print package_obj
# <module 'plugins' from 'H:ProjectsTestsPyPluginstest1plugins__init__.pyc'>

print dir(package_obj)
# ['__builtins__', '__doc__', '__file__', '__name__', '__path__', 'my_plugin']

module_obj = getattr (package_obj, modulename)

print module_obj
# <module 'plugins.my_plugin' from 'H:ProjectsTestsPyPluginstest1pluginsmy_plugin.pyc'>

print dir (module_obj)
# ['__builtins__', '__doc__', '__file__', '__name__', 'pluginClass']

obj = getattr (module_obj, classname)

if issubclass (obj, object):
        print obj
        # <class 'plugins.my_plugin.pluginClass'>

        a = obj()

        print a
        # <plugins.my_plugin.pluginClass object at 0x00AF9750>

        a.run()
else:
        print "Not class"

Скачать этот пример можно по ссылке - test1.zip

Второй пример, приближенный к реальности

В этом примере мы заранее не будем знать ни имена модулей плагинов, ни имена классов, содержащихся внутри. А так же потребуем, чтобы все плагины были производными от базового класса baseplugin. В папке plugins кроме файла __init__.py лежат еще три модуля. Один из них с именем base.py содержит в себе базовый класс:

#-*- coding: utf-8 -*-

class baseplugin (object):
        def run (self):
                pass

Во втором модуле my_plugin_1.py содержится один класс:

#-*- coding: utf-8 -*-

import base

class pluginClass1 (base.baseplugin):
        def __init__(self):
                pass

        def run (self):
                print "    Hello, first plug-in!"

А в третьем my_plugin_2.py два класса:

#-*- coding: utf-8 -*-

import base

class pluginClass2 (base.baseplugin):
        def __init__(self):
                pass

        def run (self):
                print "    Hello, second plug-in!"

class pluginClass3 (base.baseplugin):
        def __init__(self):
                pass

        def run (self):
                print "    Hello, third plug-in!"

Ниже располагается код, который получает имена всех файлов в папке plugins. Затем импортирует найденные модули (кроме файлов base.py и __init__.py). После этого перебирает все атрибуты внутри каждого импортированного модуля, создает экземпляры найденных внутри классов, вроизводных от base.baseplugin и выполняет метод run.

#-*- coding: utf-8 -*-
import os
import inspect

plugin_dir = "plugins"

import plugins.base

# Сюда добавляем имена загруженных модулей
modules = []

# Перебирем файлы в папке plugins
for fname in os.listdir(plugin_dir):

        # Нас интересуют только файлы с расширением .py
        if fname.endswith (".py"):

                # Обрежем расширение .py у имени файла
                module_name = fname[: -3]

                # Пропустим файлы base.py и __init__.py
                if module_name != "base" and module_name != "__init__":
                        print "Load module %s" % module_name

                        # Загружаем модуль и добавляем его имя в список загруженных модулей
                        package_obj = __import__(plugin_dir + "." +  module_name)
                        modules.append (module_name)

                        print "dir(package_obj) = " + str (dir(package_obj) )
                        print
                else:
                        print "Skip " + fname

# Перебираем загруженные модули
for modulename in modules:
        module_obj = getattr (package_obj, modulename)
        print modulename
        print dir (module_obj)

        # Перебираем все, что есть внутри модуля
        for elem in dir (module_obj):   
                obj = getattr (module_obj, elem)

                # Это класс?
                if inspect.isclass (obj):

                        # Класс производный от baseplugin?
                        if issubclass (obj, plugins.base.baseplugin):
                                # Создаем экземпляр и выполняем функцию run
                                a = obj()
                                a.run()
                                print
 

Во время выполнения скрипт выводит дополнительную информацию о ходе работы. В результате на экране будут выведены следующие сообщения:

Skip base.py
Load module my_plugin_1
dir(package_obj) = ['__builtins__', '__doc__', '__file__', '__name__', '__path__', 'base', 'my_plugin_1']

Load module my_plugin_2
dir(package_obj) = ['__builtins__', '__doc__', '__file__', '__name__', '__path__', 'base', 'my_plugin_1', 'my_plugin_2']

Skip __init__.py
my_plugin_1
['__builtins__', '__doc__', '__file__', '__name__', 'base', 'pluginClass1']
Hello, first plug-in!

my_plugin_2
['__builtins__', '__doc__', '__file__', '__name__', 'base', 'pluginClass2', 'pluginClass3']
Hello, second plug-in!

Hello, third plug-in!

Скачать этот пример полностью можно по адресу test3.zip.

Плагины и py2exe

Делаем exe-шник

Лично для меня одним из важных преимуществ Python по сравнению с некоторыми другими языками (не будем показывать пальцами) является то, что благодаря библиотеки py2exe под Windows возможно сделать запускаемый exe-шник. При этом пользователь не обязан устанавливать Python, чтобы запустить вашу программу. Сейчас мы научимся работать с плагинами и в том случае, если программа распространяется в виде windows-приложения.

Для начала рассмотрим простой пример, состоящий из основного скрипта main.py и одного плагина, расположенного в папке plugins.

Основной скрипт:

#-*- coding: utf-8 -*-

modulename = "plugin"
classname = "pluginClass"

package_obj = __import__("plugins." + modulename )
print dir(package_obj)

module_obj = getattr (package_obj, modulename)
print dir (module_obj)

obj = getattr (module_obj, classname)

if issubclass (obj, object):
        a = obj()
        a.run()
else:
        print "Not class"

И исходник плагина:

#-*- coding: utf-8 -*-

class pluginClass (object):
        def __init__(self):
                pass

        def run (self):
                print "Hello, Plugin!"

Для того, чтобы сделать exe-шник напишем скрипт (назовем его setup.py), содержащий следующей код (более подробно смотрите в документации к py2exe):

from distutils.core import setup
import py2exe

setup ( console = ['main.py'])

И запустим скрипе setup.py с параметром py2exe:

C:Python25python.exe setup.py py2exe

После выполнения скрипта будет создана папка dist, где будут находиться наш exe-шник и дополнительные файлы. Про один из них - library.zip надо рассказать поподробнее. Внутри этого файла располагаются все необходимые для работы скрипта файлы *.pyc (скомпилированные в байт-код модули). Именно там при выполнении команды import foo Python будет искать модуль foo (foo.pyc). Таким образом, если мы хотим добавить пакет plugins, то должны создать папку plugins внутри архива library.zip. Скачать пример, демонстрирующий сказанное выше можно по ссылке - py2exe_test_1.zip (около 2 МБ). В папке dist располагается уже готовый exe-шник, а внутри архива library.zip находится пакет с одним плагином.

Если запустить файл main.exe, то в консоли увидим следующее:

['__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__path__', 'plugin']
['__builtins__', '__doc__', '__file__', '__loader__', '__name__', 'pluginClass']
Hello, Plugin!

Избавляемся от library.zip

С точки зрения обычного пользователя неудобно, что для добавления нового плагина надо работать с архивом. К счастью, py2exe позволяет скомпилированные модули располагать не в архиве, а просто в отдельной папке. Для этого скрипт установки setup.py должен выглядеть примерно так:

from distutils.core import setup
import py2exe

setup ( console = ['main.py'], zipfile = '.', options = {"py2exe": {"skip_archive":1}})

В options мы добавляем новый параметр skip_archive, который относится к py2exe. Этот параметр указывает, что сборки не надо архивировать. Путь, где будут лежать сборки указываем в параметре zipfile. В данном случае модули будут располагаться в одной папке с созданным exe-шником. Если не используется параметр skip_archive, то через параметр zipfile можно изменить имя архива, в котором содержатся модули (по умолчанию library.zip).

Скачать пример с таким сбособом создания exe-шника можно по ссылке - py2exe_test_2.zip (около 1.6 МБ). В нем папка plugins располагается рядом с файлом main.exe и пользователю достаточно скопировать в эту папку новый подключаемый плагин.

Плагины и дополнительные модули

Давайте теперь сделаем полноценное приложение из примера, рассмотренного в разделе "Второй пример, приближенный к реальности", но с небольшим усложнением. Предположим, что один из плагинов импортирует другой модуль Python, который не импортирует основная программа. Путь, для примера, одному из плагинов понадобится библиотека wxPython. В этом случае его код будет выглядеть вот так:

#-*- coding: utf-8 -*-

import base

import wx

class wxpluginClass (base.baseplugin):
        def __init__(self):
                pass

        def run (self):
                print "    Hello, WX!"

Если мы просто скопируем такой плагин в папку plugins, то во время работы получим ошибку о том, что невозможно импортировать модуль wx. Как один из выходов - обработать этот плагин с помощью py2exe и пользователю дать кроме самого файла плагина (не скомпилированного) те файлы, которые появятся в папке dist. Причем эти файлы надо будет скопировать не в папку plugins, а в папку, где располагается основной exe-шник.

Для этого в примере, который можно скачать по ссылке - py2exe_test_3.zip (около 3.6 МБ) в папке с исходником плагина есть еще один файл setup.py, который работает с плагином, импортирующий wx. В нем нет ничего принципиально нового.

Заключение

Итак, мы научились делать так, чтобы наша программа умела динамически загружать модули (плагины) и сделали полноценное приложение под Windows, которое в качестве плагинов использует исходники на Python. Вот, собственно, и все, что хотелось бы рассказать :)




Рубрика: Статьи




Вышел MySQL 5.1.30, первый стабильный рели....

MySQL

После публикации 29 тестовых версий анонсирован первый стабильный релиз MySQL 5.1, пригодный для промышленной эксплуатации и обеспечивающий увеличение производительности для "тяжелых" SQL запросов, по сравнению с MySQL 5.0, примерно на 15-20%. Главные новшества появившиеся в MySQL 5.1:


Подробнее... | Рубрика: MySQL | Добавлено: 28.11.2008

Тестирование параллельных программ.

Тестирование

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


Подробнее... | Рубрика: Тестирование | Добавлено: 28.11.2008

Архитектура AMD64 (EM64T).

Архитектура AMD

Аннотация. В статье кратко рассматривается архитектура AMD64 компании AMD и ее реализация EM64T компании Intel. Описаны особенности архитектуры, ее возможности, достоинства и недостатки.


Подробнее... | Рубрика: Архитектура AMD | Добавлено: 27.11.2008

Остальные статьи:

Платформа 2009. Определяя будущее
Windows Vista Bridge Sample Library - упра...
Оптимизация 64-битных программ
Подгрузка через AJAX HTML-кода, содержащег...
Обзор нового релиза самой мощной Ajax библ...
Firebug 1.3 и 1.4 alpha — что нового и инт...
Релиз Microsoft Silverlight 2.0. Что новог...
XML документация в C#
Курсоры в MySQL 5
Microsoft опубликовала подробности о сесси...
Microsoft делится подробностями о том, что...
Тестируем новый javascript от нового брауз...
MySQL Query Cache
Использование провайдеров компиляции в As...
Чего мы ждем от C# 4.0
Delphi 2009 и C++Builder 2009
Джоэл Спольски и Джеф Этвуд запустили новы...
Поиск кода Google /* что нового? */
10 jQuery скриптов для улучшения интерфейс...
Генераторы отчетов FastReport 4 и QuickRep...


Цитата дня (все,добавить):

Портал фрилансеров

работа на дому


    Рубрикатор

Программирование

C/С++
Обучение
Windows API
XAML
Моделирование
Паттерны
Visual Basic 7 .NET
WxWidgets
Функции WinApi
Функции С++
Разработка под Mac OS
Eiffel
Visual Studio 2008
UI дизайн
Алгоритмы
Конкурсные статьи
Turbo Pascal
Visual Studio
CASE-средства
Visual Studio 2005
Без VCL
Delphi
Тех. документация
Тестирование
Software Testing
ООП
TCP/IP
Google Android
Windows Installer
.NET Framework
Драйвера
C# C Sharp
Справка
Проектирование
Информ. системы
Visual Basic
Assembler
Оптимизация кода
Gtk+
Компоненты
Реинжиниринг
Управление проектами
Extreeme programming
Lotus Notes
Алгебраическое проектирование


Интернет технологии

PHP
Perl
ASP
WAP
Cookies
SSI
CGI
Web Servers
VB Script
DNS
CSS
XML
Html
Java Script
Java2ME
Firewall
Flash
.htaccess
Apache
VRML
Протоколы
Поисковые системы
Технология JAVA
Учебник по PHP
Учебник по JavaScript
Учебник по XML
Java Q&A
AJAX
DHTML
XHTML
Dreamweaver
Web 2.0
Python
Вебмастеру
Cisco
Ruby on Rails
Silverlight

Базы данных

Access
InterBase
MySQL
Oracle
ADO .NET
Основы SQL
Учебник по Access 2002
MS
Microsoft FoxPro
Доступ к данным
XML в MS SQL Server 2000
ODBC и MyODBC
Обучение
Caché
DB2
PostgresSQL
Sybase
Теория
Хранилища данных
Безопасность
Реляционные данные
MySQL и mSQL

Остальное:

Разное
Обзоры книг
Безопасность
Графика и дизайн
Юмор
Linux
Фракталы
Microsoft Axapta
Многоядерность
Сети
Microsoft Office
Работа
MS-DOS
Криптография
Графика и игроделание
Новости SDK
Системы защиты
Учебник по AutoCad
CVS
Windows XP
Windows Server 2003
Windows Vista
Windows 7
Мероприятия