包格式¶
本页面讨论用于分发 Python 包的文件格式及其之间的区别。
您将在诸如 PyPI 等包索引上找到两种格式的文件:源分发包(简称 sdists)和二进制分发包(通常称为 wheels)。例如,pip 23.3.1 的 PyPI 页面允许您下载两个文件:pip-23.3.1.tar.gz 和 pip-23.3.1-py3-none-any.whl。前者是 sdist,后者是 wheel。如下文所述,它们服务于不同的目的。在 PyPI(或其他地方)发布包时,您应该始终同时上传 sdist 和一个或多个 wheel。
什么是源分发包?¶
从概念上讲,源分发包是原始形式的源代码存档。具体来说,sdist 是一个 .tar.gz 存档,其中包含源代码和一个名为 PKG-INFO 的额外特殊文件,该文件包含项目元数据。此文件的存在有助于打包工具更高效,因为它不需要自己计算元数据。PKG-INFO 文件遵循 核心元数据规范 中指定的格式,不打算手动编写[1]。
因此,您可以使用标准工具解压缩 sdist 来检查其内容,例如在 UNIX 平台(如 Linux 和 macOS)上使用 tar -xvf,或在任何平台上使用 Python 的 tarfile 模块的命令行界面。
sdists 在打包生态系统中扮演着多种角色。当标准 Python 包安装程序 pip 找不到要安装的 wheel 时,它将回退到下载源分发包,从中编译一个 wheel,然后安装该 wheel。此外,sdists 经常被下游打包者(如 Linux 发行版、Conda、macOS 上的 Homebrew 和 MacPorts 等)用作包源,他们出于各种原因可能更喜欢它们,例如从 Git 仓库拉取。
源分发包通过其文件名识别,文件名格式为 package_name-version.tar.gz,例如 pip-23.3.1.tar.gz。
如果您想了解 sdist 格式的技术细节,请阅读 sdist 规范。
什么是 wheel?¶
从概念上讲,wheel 准确包含安装包时需要复制的文件。
对于包含用 C、C++ 和 Rust 等编译语言编写的扩展模块的包,sdists 和 wheels 之间存在很大差异,这些扩展模块需要编译成平台相关的机器代码。对于这些包,wheels 不包含源代码(如 C 源文件),而是编译后的可执行代码(如 Linux 上的 .so 文件或 Windows 上的 DLL)。
此外,每个项目版本只有一个 sdist,但可能有许多 wheels。同样,这在扩展模块的上下文中最为相关。扩展模块的编译代码与操作系统和处理器架构绑定,并且通常还与 Python 解释器版本绑定(除非使用Python 稳定 ABI)。
对于纯 Python 包,sdists 和 wheels 之间的差异不那么明显。通常只有一个 wheel,适用于所有平台和 Python 版本。Python 是一种解释型语言,不需要提前编译,因此 wheels 包含 .py 文件,就像 sdists 一样。
如果您对 .pyc 字节码文件感到好奇:它们不包含在 wheels 中,因为它们生成成本低廉,并且包含它们将不必要地迫使大量包为每个 Python 版本分发一个 wheel,而不是单个 wheel。相反,像 pip 这样的安装程序在安装包时生成它们。
话虽如此,即使对于纯 Python 项目,sdists 和 wheels 之间仍然存在重要差异。wheels 旨在只包含要安装的内容,不多也不少。特别是,wheels 绝不应包含测试和文档,而 sdists 通常会包含。此外,wheel 格式比 sdist 更复杂。例如,它包含一个特殊文件(称为 RECORD),该文件列出 wheel 中的所有文件以及其内容的哈希值,作为下载完整性的安全检查。
乍一看,您可能想知道对于“简单基本”的纯 Python 项目是否真的需要 wheels。请记住,由于 sdists 的灵活性,像 pip 这样的安装程序不能直接从 sdists 安装——它们需要首先通过调用 sdist 指定的构建后端来构建一个 wheel(构建后端在构建 wheel 时可能会进行各种转换,例如编译 C 扩展)。因此,即使对于纯 Python 项目,您也应该始终将 sdist 和 wheel 都上传到 PyPI 或其他包索引。这使得您的用户安装速度更快,因为 wheel 可以直接安装。通过只包含必须安装的文件,wheels 也使得下载更小。
在技术层面,wheel 是一个 ZIP 存档(与 TAR 存档的 sdists 不同)。您可以通过将其解压缩为普通 ZIP 存档来检查其内容,例如,在 Linux 和 macOS 等 UNIX 平台上使用 unzip,在 Windows 上使用 Powershell 中的 Expand-Archive,或使用 Python 的 zipfile 模块的命令行界面。这对于检查 wheel 是否包含您需要的所有文件非常有用。
在 wheel 内部,您将找到包文件,以及一个名为 package_name-version.dist-info 的额外目录。此目录包含各种文件,包括一个 METADATA 文件(等同于 sdists 中的 PKG-INFO)以及 RECORD。这对于确保您的 wheels 中没有缺少文件非常有用。
wheel 的文件名(忽略一些很少使用的功能)如下所示:package_name-version-python_tag-abi_tag-platform_tag.whl。此命名约定标识了 wheel 兼容的平台和 Python 版本。例如,名称 pip-23.3.1-py3-none-any.whl 意味着
(
py3)此 wheel 可以安装在任何 Python 3 实现上,无论是 CPython(最广泛使用的 Python 实现),还是像 PyPy 这样的替代实现;(
none)它不依赖于 Python 版本;(
any)它不依赖于平台。
模式 py3-none-any 对于纯 Python 项目很常见。带有扩展模块的包通常会附带具有更复杂标签的多个 wheels。
所有关于 wheel 格式的技术细节都可以在 wheel 规范中找到。
那 eggs 呢?¶
“Egg”是一种旧的包格式,已被 wheel 格式取代。它不应再使用。自 2023 年 8 月起,PyPI 拒绝 egg 上传。
以下是 wheel 和 egg 之间重要区别的细分。
egg 格式由 Setuptools 于 2004 年引入,而 wheel 格式由 PEP 427 于 2012 年引入。
Wheel 有一个官方标准规范。Egg 没有。
Wheel 是一种分发格式,即一种打包格式。[2] Egg 既是一种分发格式,也是一种运行时安装格式(如果保持压缩状态),并且设计为可导入。
Wheel 存档不包含
.pyc文件。因此,当分发只包含 Python 文件(即没有编译扩展)并且与 Python 2 和 3 兼容时,wheel 可能是“通用”的,类似于sdist。Wheel 使用标准.dist-info 目录。Egg 使用
.egg-info。Wheel 具有更丰富的文件命名约定。单个 wheel 存档可以表明其与多种 Python 语言版本和实现、ABI 和系统架构的兼容性。
Wheel 是版本化的。每个 wheel 文件都包含 wheel 规范的版本和打包它的实现。
Wheel 内部按 sysconfig 路径类型组织,因此更容易转换为其他格式。