zipapp ---管理可执行的python-zip存档

3.5 新版功能.

源代码: Lib/zipapp.py


这个模块提供了一些工具来管理包含python代码的zip文件的创建,这些代码可以是 executed directly by the Python interpreter . 模块提供了 命令行界面 和A python应用程序接口 .

基本实例

下面的示例显示了 命令行界面 可用于从包含python代码的目录创建可执行存档。运行时,存档将执行 main 模块的功能 myapp 在存档中。

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

命令行界面

当从命令行作为程序调用时,将使用以下表单:

$ python -m zipapp source [options]

如果 source 是一个目录,这将从 source . 如果 source 是一个文件,它应该是一个归档文件,并将其复制到目标归档文件(如果指定了--info选项,则将显示其shebang行的内容)。

可以理解以下选项:

-o <output>, --output=<output>

将输出写入名为 output . 如果未指定此选项,则输出文件名将与输入文件名相同。 source ,带有扩展名 .pyz 补充。如果给定了显式文件名,则按原样使用(因此 .pyz 如有需要,应包括延期)。

必须指定输出文件名,如果 source 是一个档案(在这种情况下, output 不能与 source

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

添加 #! 行到存档指定 口译译员 作为要运行的命令。另外,在POSIX上,使归档文件可执行。默认情况是不写 #! 行,而不是使文件可执行。

-m <mainfn>, --main=<mainfn>

写一篇 __main__.py 文件到执行的存档 主干网 . 这个 主干网 参数的格式应为“pkg.mod:fn”,其中“pkg.mod”是存档中的包/模块,“fn”是给定模块中的可调用文件。这个 __main__.py 文件将执行该可调用文件。

--main 复制存档时无法指定。

-c, --compress

使用deflate方法压缩文件,减小输出文件的大小。默认情况下,文件以未压缩的方式存储在存档中。

--compress 复制存档时无效。

3.7 新版功能.

--info

出于诊断目的,显示嵌入在存档中的解释器。在这种情况下,将忽略任何其他选项,并且源必须是存档,而不是目录。

-h, --help

打印简短的使用信息并退出。

python应用程序接口

该模块定义了两个便利功能:

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

从创建应用程序存档 source . 源可以是以下任何一种:

  • 目录的名称,或 path-like object 指目录,在这种情况下,将从该目录的内容创建新的应用程序存档。

  • 现有应用程序存档文件的名称,或 path-like object 指这样一个文件,在这种情况下,文件被复制到目标(修改它以反映为 口译译员 参数)。文件名应包括 .pyz 如有需要,可延长。

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

这个 目标 参数确定结果存档的写入位置:

  • 如果是文件名,或 path-like object ,存档文件将写入该文件。

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

  • 如果目标被省略(或 None ,源必须是目录,目标将是与源同名的文件,其中 .pyz 扩展名已添加。

这个 口译译员 参数指定用于执行存档的python解释器的名称。它在档案的开头写为“shebang”行。在POSIX上,这将由操作系统解释,在Windows上,它将由Python启动程序处理。省略 口译译员 结果未写入shebang行。如果指定了解释器,并且目标是文件名,那么将设置目标文件的可执行位。

这个 main 参数指定将用作存档主程序的可调用文件的名称。仅当源是目录且源不包含 __main__.py 文件。这个 main 参数的格式应为“pkg.module:callable”,通过导入“pkg.module”并在不带参数的情况下执行给定的callable来运行存档。省略是一个错误 main 如果源是目录且不包含 __main__.py 文件,否则生成的存档将不可执行。

可选的 滤波器 参数指定一个回调函数,该函数传递一个路径对象,该对象表示要添加的文件的路径(相对于源目录)。它应该会回来 True 如果要添加文件。

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

如果为指定了文件对象 source目标 ,调用方负责在调用create_archive后关闭它。

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

3.7 新版功能: 增加了 滤波器压缩的 参数。

zipapp.get_interpreter(archive)

返回中指定的解释器 #! 在存档开始处的行。如果没有 #! 返回线路 None . 这个 档案文件 参数可以是文件名或类似文件的对象,以字节模式打开以进行读取。假设在存档的开始。

实例

把一个目录打包到一个归档文件中,然后运行它。

$ 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())

指定解释程序

注意,如果您指定了一个解释器,然后分发您的应用程序存档,那么您需要确保所使用的解释器是可移植的。用于Windows的python启动程序支持最常见的posix形式 #! 行,但还有其他问题需要考虑:

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

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

  • 不能说“python x.y或更高版本”,因此请小心使用类似于“/usr/bin/env python3.4”的精确版本,因为您需要为python 3.5的用户更改shebang行。

通常,您应该使用“/usr/bin/env python2”或“/usr/bin/env python3”,这取决于您的代码是为python2还是3编写的。

使用zippapp创建独立应用程序

使用 zipapp 模块,可以创建独立的python程序,这些程序可以分发给只需要在系统上安装适当版本的python的最终用户。实现这一点的关键是将应用程序的所有依赖项与应用程序代码捆绑到存档中。

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

  1. 在目录中正常创建应用程序,因此 myapp 目录包含 __main__.py 文件和任何支持的应用程序代码。

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

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

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

  3. 或者,删除 .dist-info PIP在中创建的目录 myapp 目录。这些保存了用于PIP管理包的元数据,并且由于您不会进一步使用PIP,因此它们不是必需的——尽管如果您离开它们不会造成任何伤害。

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

    $ python -m zipapp -p "interpreter" myapp
    

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

在UNIX上 myapp.pyz 文件是可执行的。您可以重命名文件以删除 .pyz 扩展名,如果您类似于“普通”命令名。在Windows上, myapp.pyz[w] 由于python解释器注册了 .pyz.pyzw 安装时的文件扩展名。

使Windows可执行

在Windows上,注册 .pyz 扩展是可选的,而且有些地方不能“透明地”识别已注册的扩展(最简单的例子是 subprocess.run(['myapp']) 找不到应用程序-需要显式指定扩展名)。

因此,在Windows上,通常最好从zippapp创建可执行文件。这相对容易,尽管它确实需要一个C编译器。基本方法依赖于这样一个事实:zipfiles可以预先准备任意数据,而windows exe文件可以附加任意数据。因此,通过创建一个合适的发射器和定位 .pyz 文件放在它的末尾,最后得到一个运行应用程序的可执行文件。

合适的发射器可以简单如下:

#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中都将以不变的方式运行。 (python3.dll )在用户的 PATH .

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

告诫

将应用程序绑定到单个文件的过程存在一些限制。在大多数情况下,如果不是全部,可以在不需要对应用程序进行重大更改的情况下解决这些问题。

  1. 如果您的应用程序依赖于一个包含C扩展名的包,则该包不能从zip文件运行(这是一个操作系统限制,因为文件系统中必须存在可执行代码,操作系统加载程序才能加载它)。在这种情况下,您可以从zipfile中排除这种依赖关系,并要求用户安装它,或者将其与zipfile一起发送,并将代码添加到 __main__.py 将包含解压模块的目录包含在 sys.path . 在这种情况下,您需要确保为目标体系结构提供适当的二进制文件(并可能选择要添加到的正确版本) sys.path 在运行时,基于用户的计算机)。

  2. 如果您要按照上述说明传送Windows可执行文件,则需要确保您的用户 python3.dll 在它们的路径上(这不是安装程序的默认行为),或者应该将应用程序与嵌入的分发捆绑在一起。

  3. 上面建议的启动程序使用python嵌入API。这意味着在您的应用程序中, sys.executable 将是您的申请,以及 not 传统的python解释器。您的代码及其依赖项需要为这种可能性做好准备。例如,如果应用程序使用 multiprocessing 模块,它需要调用 multiprocessing.set_executable() 让模块知道在哪里可以找到标准的Python解释器。

python-zip应用程序存档格式

python已经能够执行包含 __main__.py 2.6版之后的文件。为了由python执行,应用程序归档文件必须是一个包含 __main__.py 将作为应用程序入口点运行的文件。与任何Python脚本一样,脚本的父级(在本例中是zip文件)将放置在 sys.path 因此,可以从zip文件导入更多模块。

zip文件格式允许将任意数据预先附加到zip文件中。zip应用程序格式使用此功能将标准posix“shebang”行预先发送到文件 (#!/path/to/interpreter

因此,python-zip应用程序格式正式为:

  1. 一个可选的shebang行,包含字符 b'#!' 后跟一个解释程序名,然后是一个换行符 (b'\n' 字符。解释器名称可以是操作系统“shebang”处理可以接受的任何名称,也可以是Windows上的python启动程序。解释器应该在Windows上以UTF-8编码,并且 sys.getfilesystemencoding() 在POSIX上。

  2. 标准ZipFile数据,由 zipfile 模块。ZipFile内容 must 包括一个名为 __main__.py (必须在zipfile的“根目录”中,即不能在子目录中)。压缩文件数据可以压缩或解压缩。

如果一个应用程序归档文件有一个shebang行,它可能在posix系统上设置了可执行位,以允许直接执行它。

不需要使用此模块中的工具来创建应用程序存档-该模块非常方便,但通过任何方式创建的上述格式的存档对于Python都是可以接受的。