Существует несколько утилит — все с различными процедурами, ограничениями, и предназначаются для операционных систем — для получения пакета Python и всех его зависимостей и превращения их в единственную программу в двоичном представлении, которую легко поставить клиентам:
Моя ситуация идет один шаг вперед: сторонние разработчики будут желать записать плагины, расширения или дополнения для моего приложения. Это - конечно, пугающий вопрос, как пользователи на платформах как Windows наиболее легко установили бы плагины или дополнения таким способом, которым мое приложение может легко обнаружить, что они были установлены. Но кроме того основной вопрос - другой: как сторонний разработчик может связать их расширение любыми библиотеками, в которых само расширение нуждается (который мог бы быть двоичными модулями, как lxml) таким способом, которым зависимости плагина становятся доступными для импорта в то же самое время, когда плагин становится доступным.
Как к этому можно приблизиться? Для моего приложения будут нужны его собственная сменная область на диске и его собственный сменный реестр для создания этого послушным? Или есть ли общие механизмы, которые я мог постараться не писать сам, который позволит приложение, которое распределяется как единственный исполняемый файл, чтобы озираться и найти плагины, которые также установлены как единственные файлы?
У вас должна быть возможность иметь каталог плагинов, который ваше приложение сканирует во время выполнения (или позже) для импорта рассматриваемого кода. Вот пример, который должен работать с обычным кодом .py или .pyc, который работает даже с подключаемыми модулями, хранящимися в zip-файлах (так что пользователи могут просто поместить someplugin.zip в каталог 'plugins' и заставить его волшебным образом работать):
import re, os, sys
class Plugin(object):
"""
The base class from which all plugins are derived. It is used by the
plugin loading functions to find all the installed plugins.
"""
def __init__(self, foo):
self.foo = foo
# Any useful base plugin methods would go in here.
def get_plugins(plugin_dir):
"""Adds plugins to sys.path and returns them as a list"""
registered_plugins = []
#check to see if a plugins directory exists and add any found plugins
# (even if they're zipped)
if os.path.exists(plugin_dir):
plugins = os.listdir(plugin_dir)
pattern = ".py$"
for plugin in plugins:
plugin_path = os.path.join(plugin_dir, plugin)
if os.path.splitext(plugin)[1] == ".zip":
sys.path.append(plugin_path)
(plugin, ext) = os.path.splitext(plugin) # Get rid of the .zip extension
registered_plugins.append(plugin)
elif plugin != "__init__.py":
if re.search(pattern, plugin):
(shortname, ext) = os.path.splitext(plugin)
registered_plugins.append(shortname)
if os.path.isdir(plugin_path):
plugins = os.listdir(plugin_path)
for plugin in plugins:
if plugin != "__init__.py":
if re.search(pattern, plugin):
(shortname, ext) = os.path.splitext(plugin)
sys.path.append(plugin_path)
registered_plugins.append(shortname)
return registered_plugins
def init_plugin_system(cfg):
"""
Initializes the plugin system by appending all plugins into sys.path and
then using load_plugins() to import them.
cfg - A dictionary with two keys:
plugin_path - path to the plugin directory (e.g. 'plugins')
plugins - List of plugin names to import (e.g. ['foo', 'bar'])
"""
if not cfg['plugin_path'] in sys.path:
sys.path.insert(0, cfg['plugin_path'])
load_plugins(cfg['plugins'])
def load_plugins(plugins):
"""
Imports all plugins given a list.
Note: Assumes they're all in sys.path.
"""
for plugin in plugins:
__import__(plugin, None, None, [''])
if plugin not in Plugin.__subclasses__():
# This takes care of importing zipped plugins:
__import__(plugin, None, None, [plugin])
Итак, давайте скажем, у меня есть плагин с именем «foo.py» в каталоге с именем «plugins» (то есть в базовом каталоге моего приложения), который добавит в мое приложение новые возможности. Его содержимое может выглядеть так:
from plugin_stuff import Plugin
class Foo(Plugin):
"""An example plugin."""
self.menu_entry = {'Tools': {'Foo': self.bar}}
def bar(self):
return "foo plugin!"
Я мог бы инициализировать свои плагины при запуске своего приложения следующим образом:
plugin_dir = "%s/plugins" % os.getcwd()
plugin_list = get_plugins(plugin_dir)
init_plugin_system({'plugin_path': plugin_dir, 'plugins': plugin_list})
plugins = find_plugins()
plugin_menu_entries = []
for plugin in plugins:
print "Enabling plugin: %s" % plugin.__name__
plugin_menu_entries.append(plugin.menu_entry))
add_menu_entries(plugin_menu_entries) # This is an imaginary function
Это должно работать, пока плагин является файлом .py или .pyc (при условии, что он скомпилирован по байтам. для рассматриваемой платформы). Это может быть отдельный файл или внутри каталога с init .py или внутри zip-файла с теми же правилами.
Как я узнаю, что это работает? Вот как я реализовал плагины в PyCI .PyCI - это веб-приложение, но нет причин, по которым этот метод не работал бы с обычным графическим интерфейсом. В приведенном выше примере я решил использовать воображаемую функцию add_menu_entries () в сочетании с объектной переменной Plugin, которую можно использовать для добавления методов плагина в меню вашего графического интерфейса.
Надеюсь, этот ответ поможет вам создать свою собственную систему плагинов. Если вы хотите точно увидеть, как это реализовано, я рекомендую вам загрузить исходный код PyCI и посмотреть plugin_utils.py и пример подключаемого модуля в каталоге plugins_enabled.
Вот еще один пример приложения на Python, использующего плагины: OpenSTV. Здесь подключаемые модули могут быть только модулями Python.