PEP 552 – 确定性 pycs
- 作者:
- Benjamin Peterson <benjamin at python.org>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建:
- 2017 年 9 月 4 日
- Python 版本:
- 3.7
- 历史记录:
- 2017 年 9 月 7 日
- 决议:
- Python-Dev 消息
摘要
本 PEP 提出对 pyc 格式的扩展,使其更具确定性。
理由
一个 可重现构建 意味着每次构建相同的源代码时,都会生成相同的字节输出,即使是在不同的机器上(自然地,必须满足机器拥有相当类似的环境配置的要求)。可重现性对安全性至关重要。它也是基于内容的构建系统(如 Bazel)中的关键概念,这些系统在输出文件的内容是输入文件内容的确定性函数时,效率最高。
当前的 Python pyc 格式是模块的已编组代码对象,前面加上一个 魔数、源代码时间戳和源代码文件大小。源代码时间戳的存在意味着 pyc 不是输入文件内容的确定性函数,它还取决于易变的元数据,即源代码的 mtime。因此,pyc 是可重现性的障碍。
Python 代码的分发者目前面临以下选择:
- 不分发 pyc,从而失去缓存优势
- 分发 pyc,从而失去可重现性
- 小心地为所有 Python 源代码文件赋予确定性时间戳(例如,参见 https://github.com/python/cpython/pull/296)
- 执行 1 和 2 的复杂混合,例如在安装时生成 pyc
这些选项都不太吸引人。本 PEP 建议允许用确定性哈希值替换时间戳。不过,当前的时间戳失效方法将保持为默认值。尽管有非确定性,但时间戳失效方法在许多工作流程和用例中运行良好。基于哈希的 pyc 格式可能会带来读取和哈希所有源代码文件的成本,这比简单地检查时间戳要昂贵。因此,目前,我们预计它主要由分发者和高级用例使用。
规范
pyc 标头目前由 3 个 32 位字组成。我们将将其扩展为 4 个。第一个字将继续为魔数,用于对字节码和 pyc 格式进行版本控制。第二个字(概念上是新字)将是一个位字段。pyc 的剩余标头解释和失效行为取决于位字段的内容。
如果位字段为 0,则 pyc 是传统的基于时间戳的 pyc。也就是说,第三和第四个字分别是时间戳和文件大小,失效将通过将源代码文件的元数据与标头中的元数据进行比较来完成。
如果位字段的最低位被设置,则 pyc 是一个基于哈希的 pyc。我们将第二个最低位称为 check_source
标志。在位字段之后是一个 64 位的源代码文件哈希值。我们将使用带有一个硬编码密钥的 SipHash,该密钥为源代码文件的内容。其他快速哈希算法(如 MD5 或 BLAKE2)也可以工作。我们选择 SipHash 是因为 Python 已经从 PEP 456 中内置了其实现,尽管必须向 Python 公开允许选择 SipHash 密钥的接口。哈希的安全不是问题,尽管我们忽略了 MD5 之类的完全失效的哈希算法,以方便在受控环境中对 Python 进行审计。
当 Python 遇到一个基于哈希的 pyc 时,其行为取决于 check_source
标志的设置。如果 check_source
标志被设置,Python 将通过对源代码文件进行哈希并将其与 pyc 中的预期哈希进行比较来确定 pyc 的有效性。如果需要重新生成 pyc,它将再次以基于哈希的 pyc 的形式重新生成,并设置 check_source
标志。
对于 check_source
未设置的基于哈希的 pyc,Python 将简单地加载 pyc,而不会检查源代码文件的哈希值。在这种情况下,期望一些外部系统(例如,本地 Linux 发行版的包管理器)负责维护 pyc 的最新状态,因此 Python 本身不需要进行检查。即使在禁用验证的情况下,也应该正确设置哈希字段,以便带外一致性检查器可以验证 pyc 的最新状态。还要注意,PEP 3147 中关于没有对应源代码文件的 pyc 不应该被加载的规定仍然适用于基于哈希的 pyc。
py_compile
和 compileall
的编程 API 将支持生成基于哈希的 pyc。主要的是,py_compile
将定义一个新的枚举,对应于所有可用的 pyc 失效模块。
class PycInvalidationMode(Enum):
TIMESTAMP
CHECKED_HASH
UNCHECKED_HASH
py_compile.compile
、compileall.compile_dir
和 compileall.compile_file
都将获得一个 invalidation_mode
参数,它接受 PycInvalidationMode
枚举的值。
compileall
工具将扩展一个新的命令行选项 --invalidation-mode
,用于生成带有或不带有 check_source
位设置的基于哈希的 pyc。 --invalidation-mode
将是一个三态选项,接受 timestamp
(默认值)、checked-hash
和 unchecked-hash
的值,分别对应于 PycInvalidationMode
的值。
importlib.util
将扩展一个 source_hash(source)
函数,该函数计算 pyc 写入代码为字节串 source 使用的哈希值。
基于哈希的 pyc 失效的运行时配置将通过一个新的解释器选项 --check-hash-based-pycs
来实现。这是一个三态选项,它可以接受 3 个值:default
、always
和 never
。默认值 default
意味着基于哈希的 pyc 中的 check_source
标志决定失效,如上所述。 always
会导致解释器对源代码文件进行哈希以进行失效,无论 check_source
位的值如何。 never
会导致解释器始终假设基于哈希的 pyc 有效。当 --check-hash-based-pycs=never
生效时,未经检查的基于哈希的 pyc 将以未经检查的基于哈希的 pyc 的形式重新生成。基于时间戳的 pyc 不会受到 --check-hash-based-pycs
的影响。
参考资料
致谢
作者感谢 Gregory P. Smith、Christian Heimes 和 Steve Dower 对本 PEP 主题的宝贵讨论。
版权
本文件已进入公有领域。
源代码:https://github.com/python/peps/blob/main/peps/pep-0552.rst
最后修改时间:2023 年 9 月 9 日 17:39:29 GMT