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 <interpreter>, --python=<interpreter>

向归档添加一行 #!,将 interpreter 指定为要运行的命令。此外,在 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)

source 创建应用程序归档。源可以是以下任何一种

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

  • 现有应用程序归档文件的名称,或指向此类文件的类路径对象,在这种情况下,该文件将被复制到目标(修改以反映 interpreter 参数给定的值)。如果需要,文件名应包含 .pyz 扩展名。

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

target 参数确定结果归档将写入何处

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

  • 如果它是已打开的文件对象,则归档将写入该文件对象,该文件对象必须以字节模式打开以供写入。

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

interpreter 参数指定将执行归档的 Python 解释器名称。它作为“shebang”行写入归档的开头。在 POSIX 上,这将由操作系统解释,在 Windows 上,它将由 Python 启动器处理。省略 interpreter 将不写入 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 还是 3 编写的。

使用 zipapp 创建独立应用程序

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

创建独立归档的步骤如下

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

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

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

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

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

    $ python -m zipapp -p "interpreter" myapp
    

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

在 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')。解释器名称可以是操作系统“shebang”处理或 Windows 上的 Python 启动器接受的任何名称。解释器在 Windows 上应使用 UTF-8 编码,在 POSIX 上应使用 sys.getfilesystemencoding() 编码。

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

如果应用程序归档具有 shebang 行,则可以在 POSIX 系统上设置其可执行位,以允许其直接执行。

不要求使用此模块中的工具来创建应用程序归档 - 该模块只是一个便利工具,但以任何方式创建的上述格式的归档都可被 Python 接受。