Python zipapp 模块完全指南:制作与运行可执行的 .pyz Zip 应用归档
2026/9/10 9:35:41 网站建设 项目流程

Python zipapp 模块完全指南:制作与运行可执行的 .pyz Zip 应用归档

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

zipapp 是 Python 标准库中用于管理"可直接由 Python 解释器执行"的 Zip 归档的模块,它同时提供了命令行工具(python -m zipapp)和 Python 编程接口。本指南以 CPython 仓库中的官方文档 Doc/library/zipapp.rst 为主体,结合模块实现与测试用例,完整讲解如何把一个包含 Python 代码的目录打包成单文件应用归档、为其写入 shebang 行实现双击/直接执行、以及如何利用该机制构建可分发给最终用户的独立应用。读完本文,你将掌握.pyz文件的生成、运行、修改与分发全流程。

该模块自 Python 3.5 引入,源码位于 Lib/zipapp.py,完整测试见 Lib/test/test_zipapp.py。其核心能力是:解释器支持将"包含__main__.py的目录或 Zip 文件"作为脚本运行(参见 Doc/using/cmdline.rst),zipapp 所做的就是把应用目录高效地封装成这种可直接运行的 Zip 归档。

快速上手:第一个 .pyz 应用

假设你有一个名为myapp的目录,里面是常规的 Python 应用代码(后续章节会说明目录结构要求)。最简单的用法是:

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

第一条命令把myapp目录打包成myapp.pyz,并通过-m "myapp:main"指定归档的入口:运行归档时,解释器会导入myapp模块并执行其中的main()可调用对象。第二条命令直接交给python执行,从而打印出应用的输出。

命令行接口(Command-Line Interface)

通过命令行调用时,一般形式为:

$ python -m zipapp source [options]

其中source参数分两种情况:

  • source是一个目录:将目录内容打包生成一个新的归档。
  • source是一个文件(已存在的归档):将其复制为新的目标归档(可顺带改写 shebang 行);若同时指定了--info选项,则只显示该归档内嵌的解释器行而不复制。

支持的选项

选项说明
-o <output>, --output=<output>指定输出文件名。若不指定,输出文件名为输入source追加.pyz扩展名;若显式给出文件名则按原样使用(需要.pyz后缀时须自行带上)。当source是归档文件时必须指定输出文件名,且output不能与source相同。
-p <interpreter>, --python=<interpreter>在归档开头写入一行#!,指定运行该归档的解释器命令;同时在 POSIX 上把归档文件置为可执行。默认不写#!行、也不修改可执行位。
-m <mainfn>, --main=<mainfn>在归档内写入一个__main__.py,用于执行mainfnmainfn形如"pkg.mod:fn",其中pkg.mod是归档内的包/模块,fn是该模块中可调用的对象。当source是归档(即复制场景)时不能指定--main
-c, --compress使用 deflate 方法压缩文件,减小输出体积。默认文件以不压缩方式存储。对归档复制场景无效果(3.7 新增)。
--info显示归档中内嵌的解释器(shebang 内容),用于诊断。指定该选项后,其余选项被忽略,且SOURCE必须是归档文件而非目录。
-h, --help打印简短用法说明后退出。

这些参数解析逻辑可以在模块的main()函数中看到,它使用标准库argparse定义选项,并在入口做一致性校验(见 Lib/zipapp.py),例如复制场景中未提供--output、输出与源为同一文件、或--main与复制同时出现都会以错误退出。

选项之间的组合限制小结

  • 复制已有归档时:必须给出-ooutput不得等于source(CLI 通过os.path.samefile判断),且不能使用-m-c
  • 新建归档(目录源)时:目录内若已有__main__.py,则不能再传-m;若目录内既没有__main__.py又没有指定-m,会直接报错,因为这样生成的归档无法执行。

Python 编程接口(Python API)

模块提供了两个便捷函数,所有符号可通过import zipapp获得(模块还导出了异常类ZipAppError)。

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

source(归档来源)可以是以下三种之一:

  • 目录名或其 path-like 对象:据此目录内容创建新归档;
  • 已有应用归档的文件名或其 path-like 对象:此时只是复制该文件到目标(并根据interpreter参数改写 shebang),文件名需要时应带.pyz扩展名;
  • 以二进制只读模式打开的文件对象:其内容应是一个应用归档,且假定该对象当前位于归档起始位置。

target(写出位置)

  • 文件名或 path-like 对象:归档写入该文件;
  • 以二进制写模式打开的文件对象:归档写入该对象;
  • 省略或传None:此时source必须是目录,目标文件名为source同名追加.pyz

interpreter:指定执行归档的 Python 解释器名,会以 shebang 行写于归档开头。POSIX 下由操作系统解释该行,Windows 下由 Python launcher(py.exe)处理。省略则不写 shebang。若指定了 interpreter 且 target 是文件名,目标文件会被设置可执行位。

main:指定作为应用主程序的入口可调用对象,仅当 source 是目录、且该目录不含__main__.py时才能指定。格式为"pkg.module:callable",运行归档时先导入pkg.module再以无参数方式调用callable。若 source 是目录且没有__main__.py又未指定main,将报错(否则归档不可执行)。

filter(3.7 新增):回调函数,接收一个表示"待加入文件相对 source 目录路径"的pathlib.Path对象,返回True表示该文件应被打包。典型用途是排除缓存、测试或无关文件。

compressed(3.7 新增):为True时归档内文件以 deflate 压缩,否则不压缩存储;复制已有归档时该参数无效果。

文件对象的所有权:若以文件对象传入sourcetarget,调用后由调用方负责关闭它。复制归档时,文件对象只需具备readreadline(源)或write(目标)方法;从目录创建时若 target 是文件对象,它会被传给zipfile.ZipFile,因此需要满足该类所需的方法集合。

get_interpreter(archive)

读取归档起始处#!行中指定的解释器并返回;若没有#!行则返回Nonearchive可以是文件名,也可以是二进制只读模式的文件对象(须位于归档起始处)。源码实现中,shebang 行按b'#!'前缀 + 一行内容读取并解码返回,见 Lib/zipapp.py。

实战示例:从打包、运行到改写 shebang

打包并运行一个目录

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

等价于使用 Python API:

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

此例要求myapp目录内自带__main__.py(因为未指定main)。

在 POSIX 上做成可直接执行的文件

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

-p除了写入 shebang,还会在 POSIX 上自动为生成的文件设置可执行位,因此无需chmod即可直接运行。

替换已有归档的 shebang 行

把旧归档复制成新归档并更换解释器:

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

在内存中原地修改 shebang

若想就地更新归档(例如切换解释器版本),可利用io.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())

源码视角:create_archive 的实现要点

阅读 Lib/zipapp.py 的实现,可以印证上述 API 的诸多细节:

  1. 自动生成__main__.py:模块内部定义了模板MAIN_TEMPLATE(见 Lib/zipapp.py),当指定main时按import {module}{module}.{fn}()生成入口脚本,并以 UTF-8 写入归档根目录;即使将来在 Python 2 下运行,编码声明 cookie 也能保证兼容。
  2. main参数校验:实现按:分割并逐段用str.isidentifier()校验模块名与函数名合法性,非法输入抛ZipAppError("Invalid entry point: ...")(Lib/zipapp.py)。test_zipapp.py 用'''foo:'':bar''12:bar''a.b.c.:d'等一串畸形输入逐一验证了会抛出ZipAppError
  3. 文件有序写入与自引用防护:打包前先以source.rglob('*')收集全部文件并排序(test_create_sorted_archive断言归档内条目按名称有序,见 test_zipapp.py),同时预先生成待打包列表,防止 target 落在 source 目录内时把归档写入自身;若 target 命中待打包文件会抛错提示(Lib/zipapp.py),对应测试为test_target_overwrites_source_filetest_create_archive_self_insertion。值得注意的是,如果filter恰好排除了 target,这个冲突检查便不会触发(见 test_zipapp.py)。
  4. 压缩选择compressed=True对应zipfile.ZIP_DEFLATED,否则为zipfile.ZIP_STORED(Lib/zipapp.py);测试test_create_archive_with_compression会逐一核对归档内文件的压缩类型。
  5. 复制归档_copy_archive先读前两个字节判断是否以b'#!'开头,是则跳过整行 shebang,再写入新 shebang 并流式复制其余内容(Lib/zipapp.py);复制后若指定了 interpreter 且目标是文件名,同样设置S_IEXEC可执行位。
  6. 错误类型:模块自定义ZipAppError(ValueError)承载所有参数与使用错误(Lib/zipapp.py),文档级约束(目录无入口点、main与已有__main__.py并存等)都通过它抛出,测试中亦大量使用assertRaises(zipapp.ZipAppError)覆盖。

指定解释器的可移植性注意点

若要在归档中指定 interpreter 并把应用分发给他人,必须确认所用解释器在目标机器上可移植。Windows 的 Python launcher 能理解多数常见 POSIX#!行写法,但仍有几个实际问题需要权衡:

  • 若使用/usr/bin/env python(或/usr/bin/python等其他指向python的形式),要注意用户默认 Python 可能是 2 或 3,代码需在两个大版本下都兼容。
  • 若使用显式版本如/usr/bin/env python3,则没有该版本解释器的用户将无法运行(如果你的代码未做 Python 2 兼容,这或许正是你想要的)。
  • 不存在"Python X.Y 或更高版本"这种区间写法,因此像/usr/bin/env python3.4这类精确到小版本的 shebang 需要随用户环境频繁改动,务必谨慎。

官方给出的通用建议是:根据代码面向 Python 2 还是 3,使用/usr/bin/env python2/usr/bin/env python3。在创建归档前,可以用--info快速查看既有归档使用了哪个解释器,便于排查:

$ python -m zipapp existing.pyz --info Interpreter: /usr/bin/env python3

用 zipapp 构建可分发的独立(Standalone)应用

zipapp 最大的价值在于可以构建自包含的 Python 程序:只需把应用代码与全部纯 Python 依赖一起塞进归档,最终用户机器上只要装有合适版本的 Python 即可运行。构建步骤如下:

第 1 步:按常规方式在目录中开发应用,例如myapp目录下包含__main__.py与支撑代码。若希望运行归档时由模块生成入口,也可以不写__main__.py而在打包时用-m指定。

第 2 步:用 pip 把全部依赖安装进myapp目录:

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

--target指定安装目录;若没有requirements.txt,也可直接在 pip 命令行列出依赖包。)

第 3 步:打包应用:

$ python -m zipapp -p "interpreter" myapp

最终得到单文件的可执行归档myapp.pyz,可在任何装有合适解释器的机器上运行(解释器写法参考上一节)。

分发形态说明

  • 在 Unix 上,myapp.pyz本身即可直接执行;若希望命令名更"朴素",可以自行去掉.pyz后缀重命名。
  • 在 Windows 上,Python 解释器安装时会注册.pyz.pyzw文件扩展名,因此myapp.pyz(控制台程序)与.pyzw(GUI 程序)双击或从命令行调用均可直接执行,无需用户显式敲python

局限性:C 扩展依赖无法入包

如果应用依赖某个包含 C 扩展的包,该包无法在 zip 文件中运行(这是操作系统层面的限制——可执行代码必须落在真实文件系统中,OS 加载器才能装载)。应对策略是:把该依赖排除在归档之外,然后二选一——要么要求用户自行安装它,要么把解包后的二进制随 zip 一起分发,并在__main__.py中把该目录追加进sys.path。走第二种路线时,还需为各目标架构分发对应的二进制,并视运行时机器情况在sys.path里挑选正确的版本。

Python Zip 应用归档格式(.pyz 格式规范)

Python 自 2.6 起就支持执行"内含__main__.py的 zip 文件"。一个应用归档本质上就是普通的 zip 文件,其中必须含有一个作为应用入口的__main__.py。与普通脚本的执行语义一致,脚本的"父目录"(此处即 zip 文件本身)会被放入sys.path,因此归档内其他模块可被正常导入——对应地,Doc/using/cmdline.rst 也说明PYTHONPATH中允许出现包含纯 Python 模块(源码或编译形式均可)的 zip 文件条目,但扩展模块不能从 zip 导入。

zip 文件格式本就允许在文件头前追加任意数据,Python zip 应用格式正是利用这一点,在文件最前面放置一段标准 POSIX shebang 行(#!/path/to/interpreter)。因此,正式的 Python zip 应用归档格式为

  1. 可选 shebang 行:内容为字节b'#!'+ 解释器名 + 换行符b'\n'。解释器名可为操作系统 shebang 处理逻辑(Windows 上为 Python launcher)所接受的任何形式;编码方面,Windows 上须使用 UTF-8,POSIX 上使用sys.getfilesystemencoding()——这一约定在源码中体现为shebang_encoding的取值逻辑(Lib/zipapp.py)。
  2. 标准 zipfile 数据:由zipfile模块生成的普通 zip 内容。zip 内容必须包含名为__main__.py的文件,且必须位于 zip 的根目录(不能在子目录中);数据本身可以压缩也可以不压缩。

若归档带有 shebang 行,在 POSIX 上可设置可执行位以便直接运行。

关键事实:官方并未要求必须用 zipapp 模块来生成这种归档——该模块只是提供便利工具,只要符合上述格式,任何方式创建的归档 Python 都能接受并执行。

相关资源延伸

  • 模块官方文档:Doc/library/zipapp.rst(本文骨架来源,含全部参数与示例的权威描述)。
  • 模块实现:Lib/zipapp.py(create_archiveget_interpreter、CLImain()ZipAppError、shebang 编码与可执行位处理)。
  • 单元测试:Lib/test/test_zipapp.py(覆盖排序写入、filter 过滤、压缩、main 校验、shebang 读写/替换/删除、文件对象与 path-like 输入、自引用防护等 40 余个场景)。
  • 解释器执行语义:Doc/using/cmdline.rst(脚本参数支持目录与 zip 文件、sys.path行为)及PYTHONPATH中的 zip 条目说明(同文件 L806-L808)。

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询