将Python应用程序捆绑为单个文件以支持加载项或扩展?

时间:2010-05-20 19:04:52

标签: python py2exe software-distribution py2app pyinstaller

有几个实用程序 - 都具有不同的过程,限制和目标操作系统 - 用于获取Python包及其所有依赖项,并将它们转换为易于发送给客户的单个二进制程序:

我的情况更进一步:第三方开发人员希望为我的应用程序编写插件,扩展或附加组件。当然,一个令人生畏的问题是,像Windows这样的平台上的用户最容易安装插件或插件,以便我的应用程序可以轻松发现它们已经安装。但超出这个基本问题的另一个问题是:第三方开发人员如何将扩展与扩展本身需要的任何库(可能是二进制模块,如lxml)捆绑在一起,以便插件的依赖关系可以在同一时间导入插件可用的时间。

怎么能接近这个?我的应用程序是否需要在磁盘上使用自己的插件区域以及自己的插件注册表来使其易于处理?或者是否有一般的机制,我可以避免自己编写,这将允许作为单个可执行文件分发的应用程序环顾四周并找到也作为单个文件安装的插件?

2 个答案:

答案 0 :(得分:7)

您应该能够拥有应用程序在运行时(或更高版本)扫描的插件目录,以导入相关代码。这是一个应该使用常规.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])

因此,假设我在名为“plugins”的目录中有一个名为“foo.py”的插件(位于我的应用程序的基础目录中),它将为我的应用程序添加新功能。内容可能如下所示:

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文件(假设它是针对相关平台的字节编译),这应该可以工作。它可以是独立文件,也可以是具有相同规则的zip文件中 init .py 的目录。

我怎么知道这个有效?这是我在PyCI中实现插件的方式。 PyCI是一个Web应用程序,但没有理由说这种方法不适用于常规的GUI。对于上面的例子,我选择使用一个假想的add_menu_entries()函数和一个Plugin对象变量,该变量可用于将插件的方法添加到GUI的菜单中。

希望这个答案可以帮助您构建自己的插件系统。如果你想看看它是如何实现的,我建议你下载PyCI源代码,看看plugins_utils.py和plugins_enabled目录中的Example插件。

答案 1 :(得分:0)

以下是使用插件的Python应用程序的另一个示例:OpenSTV。这里的插件只能是Python模块。