zipapp-Management 可执行的 Python zip 存档

3.5 版中的新Function。

源代码: Lib/zipapp.py


该模块提供了一些工具来 Management 包含 Python 代码(可以为由 Python 解释器直接执行)的 zip 文件的创建。该模块同时提供Command-Line InterfacePython API

Basic Example

以下示例显示了如何使用Command-Line Interface从包含 Python 代码的目录中创建可执行 Files。运行时,归档文件将从归档文件中的模块myapp执行mainFunction。

$ python -m zipapp myapp -m "myapp:main"
$ python myapp.pyz
<output from myapp>

Command-Line Interface

从命令行作为程序调用时,使用以下形式:

$ python -m zipapp source [options]

如果* source 是目录,则将从 source 的内容创建 Files。如果 source *是文件,则应为归档文件,并将其复制到目标归档文件(如果指定了–info 选项,则将显示其 shebang 行的内容)。

可以理解以下选项:

  • -o <output> , --output =<output>
    • 将输出写入名为* output 的文件。如果未指定此选项,则输出文件名将与 Importing source *相同,并添加 extensions.pyz。如果给出了明确的文件名,则按原样使用(因此,如果需要,应包含.pyzextensions)。

如果* source 是归档文件,则必须指定输出文件名(在这种情况下, output 不能与 source *相同)。

  • -p <interpreter> , --python =<interpreter>

    • 在存档中添加#!行,并指定* interpreter *作为运行命令。另外,在 POSIX 上,使归档文件可执行。默认为不写#!行,并且不使文件可执行。
  • -m <mainfn> , --main =<mainfn>

    • __main__.py文件写入执行* mainfn *的 Files 中。 * mainfn *参数的格式应为“ pkg.mod:fn”,其中“ pkg.mod”是归档中的软件包/模块,而“ fn”是给定模块中的可调用对象。 __main__.py文件将执行该可调用项。

复制 Files 时无法指定--main

  • -c `,` `--compress`
    • 使用 deflate 方法 zipfile,以减小输出文件的大小。默认情况下,文件未压缩地存储在存档中。

--compress在复制存档时无效。

3.7 版中的新Function。

  • --info ``

    • 显示嵌入在归档文件中的解释器,以用于诊断。在这种情况下,任何其他选项都将被忽略,并且 SOURCE 必须是归档文件,而不是目录。
  • -h `,` `--help`

    • 打印简短用法消息并退出。

Python API

该模块定义了两个便捷Function:

  • zipapp. create_archive(* source target = None interpreter = None main = None filter = None compressed = False *)

    • 从* source *创建一个应用程序 Files。来源可以是以下任意一种:
  • 目录的名称,或表示目录的path-like object,在这种情况下,将从该目录的内容中创建一个新的应用程序存档。

  • 现有应用程序归档文件的名称,或表示该文件的path-like object,在这种情况下,该文件将复制到目标(对其进行修改以反映为* interpreter *参数给出的值)。如果需要,文件名应包含.pyzextensions。

  • 打开一个文件对象以字节模式读取。文件的内容应为应用程序存档,并且假定文件对象位于存档的开头。

  • target *参数确定将结果归档文件写入的位置:
  • 如果它是文件名或path-like object,则存档将被写入该文件。

  • 如果它是一个打开的文件对象,则存档将被写入该文件对象,该文件对象必须处于打开状态才能以字节模式写入。

  • 如果Ellipsis了目标(或None),则源必须是目录,并且目标将是与源同名的文件,并添加.pyzextensions。

  • interpreter 参数指定将用于执行存档的 Python 解释器的名称。在存档开始处将其写为“ shebang”行。在 POSIX 上,这将由 OS 解释,在 Windows 上,将由 Python 启动器处理。Ellipsis interpreter *不会导致写入任何 shebang 行。如果指定了解释器,并且目标是文件名,则将设置目标文件的可执行位。

  • main *参数指定一个可调用的名称,该名称将用作归档文件的主程序。仅当源是目录并且源不包含__main__.py文件时才可以指定它。 * main 参数应采用“ pkg.module:callable”的形式,并且将pass导入“ pkg.module”并执行不带参数的给定可调用对象来运行存档。如果源是目录并且不包含__main__.py文件,则忽略 main *是错误的,否则生成的归档文件将不可执行。

可选的* filter *参数指定一个回调函数,该函数将传递一个 Path 对象,该 Path 对象表示要添加的文件的路径(相对于源目录)。如果要添加文件,则应返回True

可选的* compressed *参数确定是否 zipfile。如果设置为True,则归档文件中的文件将使用 deflate 方法压缩;否则,文件将以未压缩的方式存储。复制现有归档文件时,此参数无效。

如果为* source target *指定了文件对象,则调用方有责任在调用 create_archive 之后将其关闭。

复制现有存档时,提供的文件对象仅需要readreadlinewrite方法。从目录创建 Files 时,如果目标是文件对象,它将被传递到zipfile.ZipFile类,并且必须提供该类所需的方法。

3.7 版的新Function:添加了* filter compressed *参数。

  • zipapp. get_interpreter(* archive *)
    • 返回 Files 开头#!行中指定的解释器。如果没有#!行,则返回None。 * archive *参数可以是打开的文件名或类似文件的对象,以字节模式读取。假定它在存档的开始。

Examples

将目录打包到存档中,然后运行它。

$ python -m zipapp myapp
$ python myapp.pyz
<output from myapp>

使用create_archive()函数可以完成相同的操作:

>>> import zipapp
>>> zipapp.create_archive('myapp', 'myapp.pyz')

要使应用程序直接在 POSIX 上可执行,请指定要使用的解释器。

$ python -m zipapp myapp -p "/usr/bin/env python"
$ ./myapp.pyz
<output from myapp>

要替换现有归档文件上的 shebang 行,请使用create_archive()函数创建修改后的归档文件:

>>> import zipapp
>>> zipapp.create_archive('old_archive.pyz', 'new_archive.pyz', '/usr/bin/python3')

要就地更新文件,请使用BytesIO对象在内存中进行替换,然后再覆盖源。请注意,在原地覆盖文件时存在错误会导致原始文件丢失的风险。该代码无法防止此类错误,但是生产代码应这样做。另外,仅当存档文件适合内存时,此方法才有效:

>>> import zipapp
>>> import io
>>> temp = io.BytesIO()
>>> zipapp.create_archive('myapp.pyz', temp, '/usr/bin/python2')
>>> with open('myapp.pyz', 'wb') as f:
>>>     f.write(temp.getvalue())

指定 Interpreter

请注意,如果您指定一个解释器然后分发您的应用程序 Files,则需要确保使用的解释器是可移植的。 Windows 的 Python 启动器支持 POSIX #!行的最常见形式,但是还需要考虑其他问题:

  • 如果您使用“/usr/bin/env python”(或“ python”命令的其他形式,例如“/usr/bin/python”),则需要考虑您的用户可能拥有 Python 2 或 Python 3 作为它们的默认设置,并编写您的代码以在这两个版本下工作。

  • 如果使用显式版本,例如“/usr/bin/env python3”,则您的应用程序将不适用于没有该版本的用户。 (如果您没有使代码与 Python 2 兼容,这可能就是您想要的)。

  • 没有办法说“ python XY 或更高版本”,因此请谨慎使用“/usr/bin/env python3.4”之类的确切版本,例如,您需要为 Python 3.5 的用户更改 shebang 行。

通常,您应该使用“/usr/bin/env python2”或“/usr/bin/env python3”,具体取决于您的代码是针对 Python 2 还是 3 编写的。

使用 zipapp 创建独立应用程序

使用zipapp模块,可以创建独立的 Python 程序,这些程序可以分发给只需要在系统上安装合适版本的 Python 的finally用户。这样做的关键是将所有应用程序的依赖项以及应用程序代码 Binding 到存档中。

创建独立存档的步骤如下:

  • 照常在目录中创建应用程序,因此您将拥有一个myapp目录,其中包含__main__.py文件以及所有支持的应用程序代码。

  • 使用 pip 将应用程序的所有依赖项安装到myapp目录中:

$ python -m pip install -r requirements.txt --target myapp

(这假定您在requirements.txt文件中有项目要求-如果没有,则可以在 pip 命令行上手动列出依赖项)。

  • (可选)删除myapp目录中 pip 创建的.dist-info目录。这些保留用于 pip 来 Management 软件包的元数据,并且您将不需要使用 pip 进行进一步使用-尽管您离开它们不会造成任何危害,但不需要使用它们。

  • 使用以下方法打包应用程序:

$ python -m zipapp -p "interpreter" myapp

这将产生一个独立的可执行文件,可以在具有适当解释器的任何计算机上运行。有关详情,请参见指定 Interpreter。它可以作为单个文件传送给用户。

在 Unix 上,myapp.pyz文件是可执行的。如果您希望使用“普通”命令名称,则可以重命名文件以删除.pyzextensions。在 Windows 上,myapp.pyz[w]文件是可执行文件,这是因为 Python 解释器在安装时会注册.pyz.pyzw文件 extensions。

使 Windows 可执行

在 Windows 上,.pyzextensions 的注册是可选的,此外,某些地方无法“透明地”识别已注册的 extensions(最简单的示例是subprocess.run(['myapp'])找不到您的应用程序-您需要明确指定 extensions) 。

因此,在 Windows 上,通常最好从 zipapp 创建可执行文件。尽管确实需要 C 编译器,但这相对容易。基本方法依赖于以下事实:zip 文件可以带有任意数据,而 Windows exe 文件可以带有任意数据。因此,pass创建合适的启动器并将.pyz文件添加到文件末尾,您finally将获得运行应用程序的单文件可执行文件。

合适的启动器可以像下面这样简单:

#define Py_LIMITED_API 1
#include "Python.h"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifdef WINDOWS
int WINAPI wWinMain(
    HINSTANCE hInstance,      /* handle to current instance */
    HINSTANCE hPrevInstance,  /* handle to previous instance */
    LPWSTR lpCmdLine,         /* pointer to command line */
    int nCmdShow              /* show state of window */
)
#else
int wmain()
#endif
{
    wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
    myargv[0] = __wargv[0];
    memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
    return Py_Main(__argc+1, myargv);
}

如果定义WINDOWS预处理器符号,它将生成一个 GUI 可执行文件,而没有它,则将生成一个控制台可执行文件。

要编译可执行文件,可以使用标准的 MSVC 命令行工具,也可以利用 distutils 知道如何编译 Python 源代码这一事实:

>>> from distutils.ccompiler import new_compiler
>>> import distutils.sysconfig
>>> import sys
>>> import os
>>> from pathlib import Path

>>> def compile(src):
>>>     src = Path(src)
>>>     cc = new_compiler()
>>>     exe = src.stem
>>>     cc.add_include_dir(distutils.sysconfig.get_python_inc())
>>>     cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
>>>     # First the CLI executable
>>>     objs = cc.compile([str(src)])
>>>     cc.link_executable(objs, exe)
>>>     # Now the GUI executable
>>>     cc.define_macro('WINDOWS')
>>>     objs = cc.compile([str(src)])
>>>     cc.link_executable(objs, exe + 'w')

>>> if __name__ == "__main__":
>>>     compile("zastub.c")

生成的启动器使用“有限的 ABI”,因此它在任何版本的 Python 3.x 中都将保持不变。它只需要 Python(python3.dll)位于用户的PATH上。

对于完全独立的分发,您可以分发启动器,并附加应用程序,并与 Python“嵌入式”分发 Binding 在一起。它可以在具有适当体系结构(32 位或 64 位)的任何 PC 上运行。

Caveats

将应用程序 Binding 到单个文件中的过程存在一些限制。在大多数情况下(即使不是全部),也可以解决这些问题,而无需对应用程序进行重大更改。

  • 如果您的应用程序依赖于包含 Cextensions 的软件包,则不能从 zip 文件运行该软件包(这是 OS 的限制,因为文件系统中必须存在可执行代码才能由 OS loader 加载)。在这种情况下,您可以从 zip 文件中排除该依赖关系,或者要求您的用户安装该依赖关系,或者将其与 zip 文件一起运送,然后将代码添加到__main__.py以在sys.path中包括包含解压缩模块的目录。在这种情况下,您将需要确保为目标体系结构交付适当的二进制文件(并有可能根据用户的机器,在运行时选择正确的版本添加到sys.path)。

  • 如果您如上所述交付 Windows 可执行文件,则需要确保用户的 PATH 上具有python3.dll(这不是安装程序的默认行为),或者应将应用程序与嵌入式发行版 Binding 在一起。

  • 上面建议的启动器使用 Python 嵌入 API。这意味着在您的应用程序中,sys.executable将成为您的应用程序,而不是常规的 Python 解释器。您的代码及其依赖项需要为此做好准备。例如,如果您的应用程序使用multiprocessing模块,则需要调用multiprocessing.set_executable()以使该模块知道在哪里可以找到标准的 Python 解释器。

Python Zip 应用程序存档格式

从 2.6 版开始,Python 便能够执行包含__main__.py文件的 zip 文件。为了由 Python 执行,应用程序归档文件只需是包含__main__.py文件的标准 zip 文件,该文件将作为应用程序的入口点运行。与任何 Python 脚本一样,脚本的父级(在本例中为 zip 文件)将放在sys.path上,因此可以从 zip 文件中导入其他模块。

zip 文件格式允许将任意数据放在 zip 文件之前。 zip 应用程序格式使用此Function在文件(#!/path/to/interpreter)之前添加标准 POSIX“ shebang”行。

因此,正式而言,Python zip 应用程序格式为:

  • 可选的 shebang 行,包含字符b'#!',后跟解释器名称,然后是换行符(b'\n')。解释器名称可以是 os“ shebang”处理可接受的任何名称,也可以是 Windows 上的 Python 启动器。解释程序应在 Windows 上使用 UTF-8 编码,在 POSIX 上使用sys.getfilesystemencoding()编码。

  • zipfile模块生成的标准 zipfile 数据。 zipfile 内容必须包含一个名为__main__.py的文件(该文件必须位于 zipfile 的“根目录”中-即它不能位于子目录中)。 zipfile 数据可以压缩或解压缩。

如果应用程序 Files 中有 shebang 行,则可能在 POSIX 系统上设置了可执行位,以使其可以直接执行。

不需要使用此模块中的工具来创建应用程序归档文件-该模块很方便,但是 Python 可以接受pass任何方式创建的上述格式的归档文件。