二进制发行格式#
此页面指定 Python 软件包的二进制发行格式,也称为 wheel 格式。
Wheel 是一个 ZIP 格式的存档,具有特殊格式的文件名和 .whl
扩展名。它包含一个几乎可以按照 PEP 376 使用特定安装方案进行安装的发行版。虽然建议使用专门的安装程序,但可以通过使用标准的“unzip”工具将 wheel 文件解压到 site-packages 中来安装,同时保留足够的信息,以便稍后将其内容分散到其最终路径上。
详细信息#
安装 wheel“distribution-1.0-py32-none-any.whl”#
Wheel 安装在概念上包括两个阶段
解压。
解析
distribution-1.0.dist-info/WHEEL
。检查安装程序是否与 Wheel 版本兼容。如果小版本号较大,则发出警告,如果主版本号较大,则中止。
如果 Root-Is-Purelib == ‘true’,则将存档解压到 purelib(site-packages)。
否则,将存档解压到 platlib(site-packages)。
分散。
解压的存档包括
distribution-1.0.dist-info/
和(如果有数据)distribution-1.0.data/
。将
distribution-1.0.data/
的每个子树移动到其目标路径上。distribution-1.0.data/
的每个子目录都是目标目录字典中的一个键,例如distribution-1.0.data/(purelib|platlib|headers|scripts|data)
。这些子目录是 sysconfig 定义的安装路径。如果适用,请更新以
#!python
开头的脚本,使其指向正确的解释器。使用已安装的路径更新
distribution-1.0.dist-info/RECORD
。删除空的
distribution-1.0.data
目录。将所有已安装的 .py 编译为 .pyc。(即使 RECORD 中未提及 .pyc,卸载程序也应足够智能以将其删除。)
建议的安装程序功能#
- 重写
#!python
。 在 wheel 中,脚本打包在
{distribution}-{version}.data/scripts/
中。如果scripts/
中文件的首行以b'#!python'
开头,请重写使其指向正确的解释器。如果存档是在 Windows 上创建的,则 Unix 安装程序可能需要为这些文件添加 +x 位。允许使用
b'#!pythonw'
约定。b'#!pythonw'
表示 GUI 脚本,而不是控制台脚本。- 生成脚本包装器。
在 wheel 中,在 Unix 系统上打包的脚本肯定没有随附的 .exe 包装器。Windows 安装程序可能希望在安装期间添加它们。
建议的归档程序功能#
- 将
.dist-info
放在存档的末尾。 建议归档程序将
.dist-info
文件实际放在存档的末尾。这支持一些潜在的有趣的 ZIP 技巧,包括在不重写整个存档的情况下修改元数据的能力。
文件格式#
文件名约定#
wheel 文件名是 {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl
。
- 发行版
发行版名称,例如“django”、“pyramid”。
- 版本
发行版版本,例如 1.0。
- 构建标记
可选的构建编号。必须以数字开头。如果两个轮子文件名在其他所有方面(即名称、版本和其他标记)都相同,则充当一个决胜者。如果未指定,则按空元组排序,否则按一个两项元组排序,第一项为初始数字,作为
int
,第二项为标记的其余部分,作为str
。构建编号的一个常见用例是由于构建环境的变化而重新构建二进制发行版,例如在使用 manylinux 映像使用预发布 CPython 版本构建发行版时。
警告
构建编号不属于发行版版本,因此难以在外部引用,尤其是在 Python 工具和标准的生态系统之外。发行版需要在外部引用的一个常见情况是在解决安全漏洞时。
由于此限制,需要在外部引用的新发行版不应在构建新发行版时使用构建编号。相反,应为这种情况创建一个新发行版版本。
- 语言实现和版本标记
例如“py27”、“py2”、“py3”。
- abi 标记
例如“cp33m”、“abi3”、“none”。
- 平台标记
例如“linux_x86_64”、“any”。
例如,distribution-1.0-1-py27-none-any.whl
是名为“distribution”的包的第一个构建,与 Python 2.7(任何 Python 2.7 实现)兼容,没有 ABI(纯 Python),在任何 CPU 架构上。
扩展名前的文件名的最后三个组件称为“兼容性标记”。兼容性标记表示包的基本解释器要求,并在 PEP 425 中进行了详细说明。
转义和 Unicode#
由于文件名组件由破折号 (-
,连字符) 分隔,因此此字符不能出现在任何组件中。处理如下
在发行版名称中,任何
-_.
字符(连字符、下划线和句点)的运行都应替换为_
(下划线),大写字符应替换为相应的小写字符。这相当于常规 名称规范化,然后将-
替换为_
。但是,使用轮子的工具必须准备好接受.
(句点)和大写字母,因为本规范的早期版本允许这些字母。版本号应根据 版本说明符规范 进行规范化。规范化的版本号不能包含
-
。其余组件可能不包含
-
字符,因此不需要转义。
生成轮子的工具应验证文件名组件不包含 -
,因为如果包含,则可能无法正确处理生成的文件。
归档文件名是 Unicode。工具更新以支持非 ASCII 文件名还需要一段时间,但本规范支持它们。
存档内部的文件名编码为 UTF-8。尽管一些常用的 ZIP 客户端无法正确显示 UTF-8 文件名,但 ZIP 规范和 Python 的 zipfile
都支持该编码。
文件内容#
轮子文件的内容,其中 {distribution} 被包的名称替换,例如 beaglevote
,{version} 被其版本替换,例如 1.0.0
,包括
/
,归档的根目录,包含要安装在purelib
或platlib
中的所有文件,如WHEEL
中指定。purelib
和platlib
通常都是site-packages
。{distribution}-{version}.dist-info/
包含元数据。{distribution}-{version}.data/
包含每个非空安装方案键的一个子目录,其中子目录名称是安装路径字典中的索引(例如data
、scripts
、headers
、purelib
、platlib
)。Python 脚本必须出现在
scripts
中,并以b'#!python'
开头,以便在安装时享受脚本包装器生成和#!python
重写。它们可以有或没有扩展名。{distribution}-{version}.dist-info/METADATA
是元数据版本 1.1 或更高格式的元数据。{distribution}-{version}.dist-info/WHEEL
是关于存档本身的元数据,采用相同的基本键:值格式Wheel-Version: 1.0 Generator: bdist_wheel 1.0 Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any Build: 1
Wheel-Version
是 Wheel 规范的版本号。Generator
是生成存档的软件的名称,还可以选择版本。Root-Is-Purelib
如果存档的顶级目录应安装到 purelib 中,则为 true;否则,根目录应安装到 platlib 中。Tag
是 wheel 的扩展兼容性标记;在示例中,文件名将包含py2.py3-none-any
。Build
是版本号,如果没有版本号,则省略。如果 Wheel-Version 大于它支持的版本,则 wheel 安装程序应发出警告,如果 Wheel-Version 的主版本号大于它支持的版本,则必须失败。
Wheel 作为一种旨在跨多个 Python 版本工作的安装格式,通常不包含 .pyc 文件。
Wheel 不包含 setup.py 或 setup.cfg。
此版本的 wheel 规范基于 distutils 安装方案,并且没有定义如何将文件安装到其他位置。该布局提供了现有 wininst 和 egg 二进制格式提供的功能的超集。
.dist-info 目录#
Wheel .dist-info 目录至少包括 METADATA、WHEEL 和 RECORD。
METADATA 是软件包元数据,与位于 sdist 根目录的 PKG-INFO 格式相同。
WHEEL 是特定于软件包构建的 wheel 元数据。
RECORD 是 wheel 中(几乎)所有文件及其安全哈希的列表。与 PEP 376 不同,除了 RECORD(不能包含其自身的哈希)之外的每个文件都必须包含其哈希。哈希算法必须是 sha256 或更好;具体来说,不允许使用 md5 和 sha1,因为签名的 wheel 文件依赖于 RECORD 中的强哈希来验证存档的完整性。
PEP 376 的 INSTALLER 和 REQUESTED 不包含在存档中。
RECORD.jws 用于数字签名。RECORD 中未提及它。
RECORD.p7s 被允许作为一种礼貌,供任何希望使用 S/MIME 签名来保护其 wheel 文件的人使用。RECORD 中未提及它。
在提取过程中,wheel 安装程序会根据文件内容验证 RECORD 中的所有哈希。除了 RECORD 及其签名之外,如果存档中的任何文件在 RECORD 中既未提及也未正确哈希,则安装将失败。
.data 目录#
任何通常未安装在 site-packages 内的文件都进入 .data 目录,其名称与 .dist-info 目录相同,但带有 .data/ 扩展名
distribution-1.0.dist-info/
distribution-1.0.data/
.data 目录包含来自发行版的脚本、头文件、文档等的子目录。在安装过程中,这些子目录的内容被移动到其目标路径。
已签名的 wheel 文件#
Wheel 文件包括一个启用了数字签名的扩展 RECORD。PEP 376 的 RECORD 被更改为包括安全哈希 digestname=urlsafe_b64encode_nopad(digest)
(无尾随 = 字符的 URL 安全 base64 编码)作为第二列,而不是 md5sum。所有可能的条目都已哈希,包括任何生成的文件(例如 .pyc 文件),但不包括不能包含其自身哈希的 RECORD。例如
file.py,sha256=AVTFPZpEKzuHr7OvQZmhaU3LvwKz06AJw8mT\_pNh2yI,3144
distribution-1.0.dist-info/RECORD,,
签名文件 RECORD.jws 和 RECORD.p7s 根本未在 RECORD 中提及,因为它们只能在生成 RECORD 后才能添加。存档中的每个其他文件都必须在 RECORD 中具有正确的哈希,否则安装将失败。
如果使用了 JSON Web 签名,则一个或多个 JSON Web 签名 JSON 序列化 (JWS-JS) 签名存储在 RECORD.jws 文件中,该文件与 RECORD 相邻。JWS 用于通过将 RECORD 的 SHA-256 哈希作为签名的 JSON 有效负载来对 RECORD 进行签名
{ "hash": "sha256=ADD-r2urObZHcxBW3Cr-vDCu5RJwT4CaRTHiFmbcIYY" }
(哈希值与 RECORD 中使用的格式相同。)
如果使用了 RECORD.p7s,则它必须包含 RECORD 的分离 S/MIME 格式签名。
轮安装程序不需要理解数字签名,但必须验证 RECORD 中的哈希与提取的文件内容相符。当安装程序根据 RECORD 检查文件哈希时,单独的签名检查器只需确定 RECORD 与签名相符即可。
参见
常见问题解答#
轮定义了一个 .data 目录。我应该将所有数据都放在那里吗?#
本规范不对您如何组织代码提出意见。.data 目录只是放置任何通常未安装在
site-packages
或 PYTHONPATH 中的文件的位置。换句话说,您仍可以使用pkgutil.get_data(package, resource)
,即使这些文件通常不会分发在轮的.data
目录中。
轮为何包含附加签名?#
附加签名比分离签名更方便,因为它们随存档一起传输。由于仅对各个文件进行签名,因此可以重新压缩存档,而不会使签名失效,或者可以在不下载整个存档的情况下验证各个文件。
轮为何允许 JWS 签名?#
JWS 所属的 JOSE 规范旨在易于实现,这也是轮的主要设计目标之一。JWS 产生了一个有用的、简洁的纯 Python 实现。
轮为何还允许 S/MIME 签名?#
对于需要或希望使用现有公钥基础设施和轮的用户,允许使用 S/MIME 签名。
签名包只是安全包更新系统中的一个基本构建块。轮仅提供构建块。
“purelib”与“platlib”有什么区别?#
轮保留了“purelib”与“platlib”的区别,这在某些平台上很重要。例如,Fedora 将纯 Python 包安装到“/usr/lib/pythonX.Y/site-packages”,将依赖于平台的包安装到“/usr/lib64/pythonX.Y/site-packages”。
所有文件都位于
{name}-{version}.data/purelib
中的“Root-Is-Purelib: false”轮等效于所有这些文件都位于根目录中的“Root-Is-Purelib: true”轮,并且可以在“purelib”和“platlib”类别中同时拥有文件。实际上,轮应该只有“purelib”或“platlib”之一,具体取决于它是否是纯 Python,并且这些文件应该位于根目录,并为“Root-is-purelib”给出了适当的设置。
是否可以直接从轮文件中导入 Python 代码?#
从技术上讲,由于支持通过简单提取进行安装并使用与
zipimport
兼容的存档格式,轮文件的一个子集确实支持直接放置在sys.path
上。但是,虽然这种行为是格式设计的自然结果,但通常不建议实际依赖它。首先,轮主要设计为一种分发格式,因此跳过安装步骤也意味着故意避免依赖任何假设完全安装的功能(例如能够使用标准工具,如
pip
和virtualenv
,以捕获和管理依赖项,以便可以为审计和安全更新目的进行适当跟踪,或通过在适当的位置发布头文件,与用于 C 扩展的标准构建机制完全集成)。其次,虽然一些 Python 软件被编写为支持直接从 zip 存档中运行,但代码通常被编写为假设它已完全安装。当试图从 zip 存档中运行软件时打破此假设时,故障通常是模糊且难以诊断的(尤其是在它们发生在第三方库中时)。造成此问题的两个最常见原因是,CPython 不支持从 zip 存档中导入 C 扩展(因为在任何平台上动态加载机制都不直接支持这样做),以及当从 zip 存档中运行时,
__file__
属性不再引用普通的文件系统路径,而是引用一个组合路径,其中包括 zip 存档在文件系统上的位置和存档中模块的相对路径。即使软件在内部正确使用抽象资源 API,与外部组件的接口仍可能需要实际磁盘文件。与元类、猴子补丁和元路径导入器一样,如果您还不确定是否需要利用此功能,那么您几乎肯定不需要它。如果您确实决定使用它,请注意,许多项目在接受它为真正的错误之前,需要使用完全安装的包来重现失败。
历史#
2013 年 2 月:此规范已通过 PEP 427 批准。
2021 年 2 月:修改了车轮文件名转义规则,使其与流行工具实际执行的操作保持一致。
附录#
示例 urlsafe-base64-nopad 实现
# urlsafe-base64-nopad for Python 3
import base64
def urlsafe_b64encode_nopad(data):
return base64.urlsafe_b64encode(data).rstrip(b'=')
def urlsafe_b64decode_nopad(data):
pad = b'=' * (4 - (len(data) & 3))
return base64.urlsafe_b64decode(data + pad)