zipapp — 管理可执行 Python zip 存档

3.5 版本中添加。

源代码: Lib/zipapp.py


此模块提供了用于管理包含 Python 代码的 zip 文件的创建的工具,该文件可由 Python 解释器直接执行。此模块同时提供了 命令行界面Python API

基本示例

以下示例展示了如何使用 命令行界面 从包含 Python 代码的目录中创建可执行存档。运行时,存档将执行存档中模块 myapp 中的 main 函数。

$ 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 <解释器>, --python=<解释器>

向存档添加一行 #!,指定 解释器 作为要运行的命令。此外,在 POSIX 上,使存档可执行。默认情况下,不写入 #! 行,也不使文件可执行。

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

向存档写入一个 __main__.py 文件,该文件执行 mainfnmainfn 参数应采用“pkg.mod:fn”的形式,其中“pkg.mod”是存档中的包/模块,“fn”是给定模块中的可调用对象。__main__.py 文件将执行该可调用对象。

--main 在复制存档时不能指定。

-c, --compress

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

--compress 在复制存档时不起作用。

在 3.7 版中添加。

--info

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

-h, --help

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

Python API

该模块定义了两个便捷函数

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

创建应用程序存档。源可以是以下任何一项

  • 目录的名称,或引用目录的类路径对象,在这种情况下,将从该目录的内容创建新的应用程序存档。

  • 现有应用程序存档文件的名称,或引用此类文件的类路径对象,在这种情况下,文件将复制到目标(修改以反映为解释器参数给定的值)。如果需要,文件名应包括.pyz扩展名。

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

目标参数决定了结果存档的写入位置

  • 如果它是文件或类路径对象的名称,则存档将写入该文件。

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

  • 如果省略了目标(或None),则源必须是一个目录,并且目标将是一个文件,其名称与源相同,并添加了.pyz扩展名。

解释器参数指定将用其执行存档的 Python 解释器的名称。它被写成存档开头的“shebang”行。在 POSIX 上,这将由操作系统解释,而在 Windows 上,它将由 Python 启动器处理。省略解释器会导致不写入 shebang 行。如果指定了解释器,并且目标是文件名,则将设置目标文件的可执行位。

main参数指定将用作存档主程序的可调用项的名称。仅当源是目录并且源不包含__main__.py文件时,才能指定它。main参数应采用“pkg.module:callable”形式,并且存档将通过导入“pkg.module”并使用无参数执行给定的可调用项来运行。如果源是目录并且不包含__main__.py文件,则省略main是错误的,否则生成的存档将不可执行。

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

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

如果为 sourcetarget 指定了文件对象,则在调用 create_archive 后,由调用者负责关闭它。

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

3.7 版中已更改: 添加了 filtercompressed 参数。

zipapp.get_interpreter(archive)

返回存档开头 #! 行中指定的解释器。如果没有 #! 行,则返回 Nonearchive 参数可以是文件名或以字节模式打开以供读取的文件对象。假定它位于存档的开头。

示例

将目录打包到存档中并运行它。

$ 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”,具体取决于您的代码是为 Python 2 还是 Python 3 编写的。

使用 zipapp 创建独立应用程序

使用 zipapp 模块,可以创建自包含的 Python 程序,可以分发给最终用户,他们只需要在其系统上安装合适版本的 Python。实现此目的的关键是将应用程序的所有依赖项与应用程序代码一起打包到存档中。

创建独立存档的步骤如下

  1. 在目录中像往常一样创建您的应用程序,这样您就有了一个 myapp 目录,其中包含一个 __main__.py 文件和任何支持应用程序的代码。

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

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

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

  3. 使用以下命令打包应用程序

    $ python -m zipapp -p "interpreter" myapp
    

这将生成一个独立的可执行文件,可以在任何装有适当解释器的机器上运行。有关详细信息,请参阅 指定解释器。它可以作为单个文件发送给用户。

在 Unix 上,myapp.pyz 文件本身是可执行的。如果您更喜欢“普通”命令名称,可以重命名文件以删除 .pyz 扩展名。在 Windows 上,myapp.pyz[w] 文件是可执行的,因为 Python 解释器在安装时会注册 .pyz.pyzw 文件扩展名。

注意事项

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

Python Zip 应用程序存档格式

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

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

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

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

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

如果应用程序存档有 shebang 行,它可以在 POSIX 系统上设置可执行位,以便直接执行它。

没有要求必须使用此模块中的工具来创建应用程序存档 - 该模块是一种便利,但 Python 可以接受任何方式创建的上述格式的存档。