有几个实用程序——都有不同的过程、限制和目标操作系统——用于获取 Python 包及其所有依赖项,并将它们转换为易于交付给客户的单个二进制程序:
- http://wiki.python.org/moin/Freeze
- http://www.pyinstaller.org/
- http://www.py2exe.org/
- http://svn.pythonmac.org/py2app/py2app/trunk/doc/index.html
我的情况更进一步:第三方开发人员将希望为我的应用程序编写插件、扩展或附加组件。当然,这是一个令人生畏的问题,例如 Windows 等平台上的用户如何最轻松地安装插件或附加组件,以便我的应用程序可以轻松发现它们已安装。但除了这个基本问题之外还有另一个问题:第三方开发人员如何将他们的扩展与扩展本身需要的任何库(可能是二进制模块,如 lxml)捆绑在一起,这样插件的依赖项就可以同时导入插件可用的时间。
如何解决这个问题?我的应用程序是否需要在磁盘上有自己的插件区域和自己的插件注册表才能使其易于处理?或者是否存在我可以避免自己编写的通用机制,允许作为单个可执行文件分发的应用程序环顾四周并找到也作为单个文件安装的插件?
最佳答案
您应该能够拥有一个插件目录,您的应用程序可以在运行时(或稍后)扫描该目录以导入相关代码。这是一个示例,它应该使用常规 .py 或 .pyc 代码,甚至可以使用存储在 zip 文件中的插件(因此用户只需将 someplugin.zip 放在“插件”目录中并让它神奇地工作):
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 是一个 Web 应用程序,但没有理由认为此方法不适用于常规的 ol' GUI。对于上面的示例,我选择将一个虚构的 add_menu_entries() 函数与一个插件对象变量结合使用,该变量可用于将插件的方法添加到您的 GUI 菜单。
希望这个答案能帮助您构建自己的插件系统。如果您想准确了解它是如何实现的,我建议您下载 PyCI 源代码并查看 plugin_utils.py 和 plugins_enabled 目录中的示例插件。
关于python - 将 Python 应用程序捆绑为单个文件以支持附加组件或扩展?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2876967/