28.4. zipapp — 管理可执行的 python zip 档案 — Python 文档
28.4. 压缩包 — 管理可执行的 python zip 档案
3.5 版中的新功能。
该模块提供工具来管理包含 Python 代码的 zip 文件的创建,这些文件可以 直接由 Python 解释器 执行。 该模块提供 命令行接口 和 Python API 。
28.4.1. 基本示例
以下示例显示了如何使用 命令行界面 从包含 Python 代码的目录创建可执行存档。 运行时,归档将执行归档中模块myapp
中的main
功能。
$ python -m zipapp myapp -m "myapp:main"
$ python myapp.pyz
<output from myapp>
28.4.2. 命令行界面
当从命令行作为程序调用时,使用以下形式:
$ 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
文件写入执行 mainfn 的存档。 mainfn 参数的格式应为“pkg.mod:fn”,其中“pkg.mod”是存档中的包/模块,“fn”是给定模块中的可调用对象。__main__.py
文件将执行该可调用文件。
- --info
- 显示嵌入在存档中的解释器,用于诊断目的。 在这种情况下,将忽略任何其他选项,并且 SOURCE 必须是存档,而不是目录。
- -h, --help
- 打印一条简短的使用消息并退出。
28.4.3. 蟒蛇API
该模块定义了两个方便的函数:
- zipapp.create_archive(source, target=None, interpreter=None, main=None)
从 源 创建应用程序存档。 来源可以是以下任何一种:
目录的名称,或引用目录的 pathlib.Path 对象,在这种情况下,将从该目录的内容创建新的应用程序存档。
现有应用程序存档文件的名称,或引用此类文件的 pathlib.Path 对象,在这种情况下,文件将被复制到目标(修改它以反映为 指定的值)解释器 参数)。 如果需要,文件名应包含
.pyz
扩展名。以字节模式打开以供读取的文件对象。 文件的内容应该是一个应用程序存档,并且假定文件对象位于存档的开头。
target 参数决定了结果存档的写入位置:
如果它是文件名或
pathlb.Path
对象,则存档将写入该文件。如果它是一个打开的文件对象,存档将被写入该文件对象,该文件对象必须以字节模式打开才能写入。
如果省略目标(或
None
),则源必须是目录,目标将是与源同名的文件,并添加了.pyz
扩展名。
interpreter 参数指定将用于执行存档的 Python 解释器的名称。 它在存档的开头写为“shebang”行。 在 POSIX 上,这将由操作系统解释,而在 Windows 上,它将由 Python 启动程序处理。 省略 解释器 会导致没有写入任何 shebang 行。 如果指定了解释器,并且目标是文件名,则将设置目标文件的可执行位。
main 参数指定一个可调用的名称,该名称将用作存档的主程序。 仅当源为目录且源不包含
__main__.py
文件时才可指定。 main 参数应采用“pkg.module:callable”形式,存档将通过导入“pkg.module”并执行给定的不带参数的可调用文件来运行。 如果源是一个目录并且不包含__main__.py
文件,则省略 main 是一个错误,否则生成的存档将无法执行。如果为 source 或 target 指定了文件对象,则调用者有责任在调用 create_archive 后关闭它。
复制现有存档时,提供的文件对象只需要
read
和readline
或write
方法。 从目录创建存档时,如果目标是文件对象,它将被传递给zipfile.ZipFile
类,并且必须提供该类所需的方法。
- zipapp.get_interpreter(archive)
- 返回存档开头的
#!
行中指定的解释器。 如果没有#!
行,则返回 None。 archive 参数可以是文件名或以字节模式打开以供读取的类文件对象。 假定它位于存档的开头。
28.4.4. 例子
将一个目录打包成一个归档文件,然后运行它。
$ python -m zipapp myapp
$ python myapp.pyz
<output from myapp>
同样可以使用 create_archive() 函数来完成:
>>> import zipapp
>>> zipapp.create_archive('myapp.pyz', 'myapp')
要使应用程序直接在 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())
28.4.5. 指定解释器
请注意,如果您指定一个解释器,然后分发您的应用程序存档,则需要确保所使用的解释器是可移植的。 适用于 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 编写的。
28.4.6. 使用 zipapp 创建独立应用程序
使用 zipapp 模块,可以创建自包含的 Python 程序,这些程序可以分发给只需要在其系统上安装合适版本的 Python 的最终用户。 这样做的关键是将应用程序的所有依赖项与应用程序代码一起打包到存档中。
创建独立存档的步骤如下:
照常在目录中创建您的应用程序,因此您有一个
myapp
目录,其中包含一个__main__.py
文件和任何支持的应用程序代码。使用 pip 将应用程序的所有依赖项安装到
myapp
目录中:$ python -m pip install -r requirements.txt --target myapp
(这假设您在
requirements.txt
文件中有您的项目要求 - 如果没有,您可以在 pip 命令行上手动列出依赖项)。或者,删除
myapp
目录下pip创建的.dist-info
目录。 这些包含 pip 管理包的元数据,并且由于您不会进一步使用 pip,因此不需要它们 - 尽管如果您离开它们不会造成任何伤害。使用以下方法打包应用程序:
$ python -m zipapp -p "interpreter" myapp
这将生成一个独立的可执行文件,它可以在任何具有适当解释器的机器上运行。 有关详细信息,请参阅 指定解释器 。 它可以作为单个文件发送给用户。
在 Unix 上,myapp.pyz
文件按原样是可执行的。 如果您更喜欢“普通”命令名称,您可以重命名文件以删除 .pyz
扩展名。 在 Windows 上,由于 Python 解释器在安装时注册了 .pyz
和 .pyzw
文件扩展名,因此 myapp.pyz[w]
文件是可执行的。
28.4.6.1. 制作 Windows 可执行文件
在 Windows 上,.pyz
扩展名的注册是可选的,此外,有些地方不能“透明地”识别注册的扩展名(最简单的例子是 subprocess.run(['myapp'])
不会找到你的应用程序 - 您需要明确指定扩展名)。
因此,在 Windows 上,通常最好从 zipapp 创建可执行文件。 这相对容易,尽管它确实需要 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")
生成的启动器使用“Limited ABI”,因此它可以在任何版本的 Python 3.x 上不变地运行。 它所需要的只是让 Python (python3.dll
) 位于用户的 PATH
上。
对于完全独立的发行版,您可以分发附加了应用程序的启动器,并与 Python“嵌入式”发行版捆绑在一起。 这将在具有适当架构(32 位或 64 位)的任何 PC 上运行。
28.4.6.2. 注意事项
将应用程序捆绑到单个文件中的过程存在一些限制。 在大多数情况下(如果不是全部),无需对应用程序进行重大更改即可解决这些问题。
- 如果您的应用程序依赖于包含 C 扩展名的包,则该包不能从 zip 文件运行(这是操作系统限制,因为文件系统中必须存在可执行代码,操作系统加载程序才能加载它)。 在这种情况下,您可以从 zipfile 中排除该依赖项,并要求您的用户安装它,或者将它与您的 zipfile 一起发送并将代码添加到
__main__.py
以包含包含解压缩模块的目录 [ X240X]。 在这种情况下,您需要确保为您的目标架构提供合适的二进制文件(并可能根据用户的机器在运行时选择正确的版本添加到sys.path
)。 - 如果您按上述方式发布 Windows 可执行文件,您需要确保您的用户在他们的 PATH 中有
python3.dll
(这不是安装程序的默认行为),或者您应该将您的应用程序与嵌入式发行版捆绑在一起. - 上面建议的启动器使用 Python 嵌入 API。 这意味着在您的应用程序中,
sys.executable
将是您的应用程序,而 不是 一个传统的 Python 解释器。 您的代码及其依赖项需要为这种可能性做好准备。 例如,如果您的应用程序使用 multiprocessing 模块,则需要调用 multiprocessing.set_executable() 来让模块知道在哪里可以找到标准的 Python 解释器。
28.4.7. Python Zip 应用程序存档格式
自 2.6 版以来,Python 已经能够执行包含 __main__.py
文件的 zip 文件。 为了由 Python 执行,应用程序存档只需是一个标准的 zip 文件,其中包含一个 __main__.py
文件,该文件将作为应用程序的入口点运行。 与任何 Python 脚本一样,脚本的父级(在本例中为 zip 文件)将放置在 sys.path 上,因此可以从 zip 文件导入更多模块。
zip 文件格式允许将任意数据添加到 zip 文件中。 zip 应用程序格式使用此功能在文件 (#!/path/to/interpreter
) 前添加标准的 POSIX “shebang”行。
因此,形式上,Python zip 应用程序格式为:
- 一个可选的 shebang 行,包含字符
b'#!'
后跟一个解释器名称,然后是一个换行符 (b'\n'
)。 解释器名称可以是操作系统“shebang”处理或 Windows 上的 Python 启动器可接受的任何名称。 解释器应该在 Windows 上用 UTF-8 编码,在 POSIX 上用 sys.getfilesystemencoding() 编码。 - 标准 zipfile 数据,由 zipfile 模块生成。 zipfile 内容 必须 包含一个名为
__main__.py
的文件(它必须在 zip 文件的“根”中 - 即它不能在子目录中)。 zipfile 数据可以压缩或解压缩。
如果应用程序存档有shebang 行,则它可能在POSIX 系统上设置了可执行位,以允许直接执行。
不要求使用此模块中的工具来创建应用程序存档 - 该模块很方便,但是 Python 可以接受通过任何方式创建的上述格式的存档。