PEP 648 – 启动时可扩展的解释器自定义
- 作者:
- Mario Corchero <mariocj89 at gmail.com>
- 发起人:
- Pablo Galindo
- 讨论至:
- Discourse 帖子
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2020年12月30日
- Python 版本:
- 3.11
- 发布历史:
- 2020年12月16日,2020年12月18日
摘要
本 PEP 提议通过允许用户安装在启动时执行的文件来支持解释器的可扩展自定义。
PEP 驳回
PEP 648 被 指导委员会 拒绝,因为它用例有限,并且进一步使启动序列复杂化。
动机
系统管理员、重新打包解释器的工具以及一些库需要在启动时自定义解释器的某些方面。
这通常通过 sitecustomize.py 由系统管理员实现,而库则依赖于利用 pth 文件。本 PEP 提出了一种以更用户友好和结构化的方式实现相同功能的方法。
pth 文件的局限性
如果一个库需要在导入之前或与解释器的一般工作相关的任何自定义,它们通常依赖于 pth 文件的特性。pth 文件在启动时加载,并通过 site 模块 [7] 实现,它们可以包含在 pth 文件评估时执行的 Python 代码。
请注意,pth 文件最初仅用于向 sys.path 添加额外的目录,但它们也可以包含以“import”开头的行,这些行将传递给 exec()。用户利用此功能实现了他们所需的自定义。例如,请参阅 setuptools [4] 或 betterexceptions [5]。
将 pth 文件用于此目的对于库开发人员来说远非理想,因为他们需要将代码注入到以 import 开头的单行中,这使得代码难以阅读。遵循这种做法的库开发人员通常会创建一个模块,该模块在导入时执行所有操作,就像 betterexceptions [5] 所做的那样,但这种方法仍然不够用户友好。
此外,如果解释器的用户想要检查 Python 启动时执行了什么,这也是不理想的,因为他们需要审查所有 pth 文件中可能存在的代码执行,这些文件可能分散在所有 site 路径中。大多数这些 pth 文件将是“合法的” pth 文件,它们只是修改路径,使得回答“启动时我的解释器正在改变什么”这个问题变得相当复杂。
sitecustomize.py 的局限性
虽然 sitecustomize 是一个可接受的解决方案,但它假设一个单一的人负责系统和解释器。如果系统管理员和解释器供应的责任方都想在解释器启动时添加自定义,他们需要就文件的内容达成一致并合并所有更改。但这并不是一个主要的限制,也不是此更改的主要驱动因素。如果发生此更改,它也将改善这些用户的情况,因为他们可以拥有以其想要增强的功能命名的自定义隔离文件,而不是拥有一个执行所有这些操作的 sitecustomize.py。例如,Ubuntu 可以将其当前的 sitecustomize.py 更改为 ubuntu_apport_python_hook。这不仅更好地表达了其意图,还让解释器的用户更好地了解其解释器上正在发生的修改。
基本原理
本 PEP 提议通过在启动时执行在 sitepackages [8] 或 usersitepackages [9] 中名为 __sitecustomize__ 的目录中发现的所有文件,来支持解释器的可扩展自定义。
为什么是 __sitecustomize__
该名称旨在遵循已有的 sitecustomize.py 概念。由于该目录将位于 site 路径中,因此我们将选择在其名称周围使用双下划线,以防止与已有的 sitecustomize.py 发生冲突。
发现新的 __sitecustomize__ 目录
Python 解释器将在启动时在任何标准 site-packages 路径中查找名为 __sitecustomize__ 的目录。
这些通常是 Python 系统位置和用户位置,但最终由 site 模块逻辑定义。
用户可以使用 site.sitepackages [8] 和 site.usersitepackages [9] 来了解解释器可以发现 __sitecustomize__ 目录的路径。
__sitecustomize__ 的发现时间
作为 site.addsitedir [10] 的一部分,在 site-packages 路径中发现 pth 文件后,将立即发现 __sitecustomize__ 目录。
这对于每个 site-packages 路径都会重复,顺序与目前 pth 文件遵循的顺序完全相同。
__sitecustomize__ 内的执行顺序
该实现将在发现每个 __sitecustomize__ 目录时,通过按名称排序来执行 __sitecustomize__ 中的文件。但是,我们不鼓励用户依赖执行顺序。
我们考虑过以随机顺序执行它们,但这可能会根据解释器选择这些文件的方式而导致不同的结果。因此,即使依赖其他文件执行不是一个好习惯,我们认为这比在解释器启动时出现随机不同的结果要好。我们选择在 pth 文件之后运行这些文件,以防用户需要在运行文件之前将项目添加到路径中。
与 pth 文件的交互
pth 文件可用于将路径添加到 sys.path,但这不应影响 __sitecustomize__ 的发现过程,因为这些目录仅在 site-packages 路径中查找。
__sitecustomize__ 内文件的执行
当发现 __sitecustomize__ 目录时,其中所有带有 .py 扩展名的文件都将使用 io.open_code 读取,并使用 exec [11] 执行。
一个空字典将作为 globals 传递给 exec 函数,以防止不同文件之间发生意外交互。
故障处理
除非解释器以详细模式运行,否则任何文件的执行错误都不会被记录,并且不应停止其他文件的评估。用户将在 stderr 中收到一条消息,说明文件执行失败,并且可以使用详细模式获取更多信息。此行为模仿了 sitecustomize.py 的现有行为。
与虚拟环境的交互
通过新的 __sitecustomize__ 解决方案应用于解释器的自定义,当用户创建虚拟环境时,将继续以与 sitecustomize.py 与虚拟环境交互相同的方式工作。
这与 pth 文件不同,pth 文件不会传播到虚拟环境,除非启用 include-system-site-packages。
如果库维护者通过 __sitecustomize__ 安装了他们不希望传播到虚拟环境的功能,他们应该通过检查 sys.prefix == sys.base_prefix 来检测是否在虚拟环境中运行。此行为类似于修改全局 sitecustomize.py 的包。
与 sitecustomize.py 和 usercustomize.py 的交互
在删除之前,sitecustomize 和 usercustomize 将在 __sitecustomize__ 之后执行,类似于 pth 文件。有关 sitecustomize 和 usercustomize 的删除计划,请参阅向后兼容性部分。
识别所有已安装的文件
为了方便 Python 启动的调试,如果调用 site 模块,它将打印在启动时将发现的 __sitecustomize__ 目录。
文件命名约定
鼓励包在文件名中包含包的名称,以避免包之间发生冲突。但对文件名的唯一要求是它以 .py 结尾,以便解释器执行它们。
禁用启动文件
在某些情况下,例如启动时间是关键时,可能需要完全禁用此选项。已经存在的标志 -S [3] 将禁用所有 site 相关操作,包括此新功能。如果传递了该标志,则不会发现 __sitecustomize__ 目录。
此外,为了允许仅禁用此新功能来启动解释器,将在 -X 下添加一个新选项:disablesitecustomize,它将专门禁用 __sitecustomize__ 的发现。
最后,用户可以通过 site.py 模块中的多个选项之一禁用用户站点来禁用仅在用户站点中发现 __sitecustomize__ 目录。
构建后端支持
虽然构建后端可以选择提供一个选项来方便将这些文件安装到 __sitecustomize__ 目录中,但本 PEP 并未直接解决这个问题。与 pth 文件类似,构建后端可以选择不为 __sitecustomize__ 文件提供易于配置的机制,并让用户介入安装过程以包含此类文件。我们不认为构建后端增强支持是本 PEP 的要求。
对启动时间的影响
此实现的一个担忧是,这种添加可能会如何影响 Python 解释器的启动时间。我们预计性能影响将高度取决于用户或系统管理员在被测试的 Python 环境中安装的文件中的逻辑。
如果解释器在其 __sitecustomize__ 目录中有任何文件,则文件执行时间加上读取代码的调用将添加到启动时间中。这类似于代码执行通过 sitecustomize.py、usercustomize.py 和 pth 文件中的代码影响启动时间的方式。因此,我们将在此处重点将此解决方案与这三者进行比较,因为否则添加到启动的实际时间高度依赖于这些文件中执行的代码。
结果是通过运行“./python.exe -c pass”进行 perf 50 次迭代,每次迭代重复命令 50 次,并获取所有结果的几何平均值。用于运行这些基准测试的文件已在参考实现中签入 [6]。
该基准测试在 3.10 alpha 7 上运行,使用 PGO 和 LTO 编译,并使用以下参数和系统状态
- 性能事件:最大采样率设置为每秒 1 次
- CPU 频率:CPU 17,35 的最低频率设置为最高频率
- 睿频加速 (MSR):CPU 17 上禁用睿频加速:MSR 0x1a0 设置为 0x4000850089
- IRQ 亲和性:将默认亲和性设置为 CPU 0-16,18-34
- IRQ 亲和性:将 IRQ 1,3-16,21,25-31,56-59,68-85,87,89-90,92-93,95-104 的亲和性设置为 CPU 0-16,18-34
- CPU:使用 2 个逻辑 CPU:17,35
- 性能事件:最大采样率:每秒 1 次
- ASLR:完全随机化
- Linux 调度器:隔离 CPU (2/36):17,35
- Linux 调度器:CPU 上禁用 RCU (2/36):17,35
- CPU 频率:0-16,18-34=min=1200 MHz, max=3600 MHz; 17,35=min=max=3600 MHz
- 睿频加速 (MSR):CPU 17,35:禁用
放置在 pth 文件、sitecustomize.py、usercustomize.py 以及 __sitecustomize__ 中的文件中执行的代码如下
import time; x = time.time() ** 5
该文件旨在执行一个简单的操作,但预计可以忽略不计。这是为了使实验处于一种情况,即使我们使任何对性能的影响可见,同时仍然使其相对真实。此外,它以 import 开头并且是单行,以便可以在 pth 文件中使用。
| 测试 | 文件数 | 时间 (微秒) | ||||
|---|---|---|---|---|---|---|
| # | sitecustomize.py |
usercustomize.py |
pth |
__sitecustomize__ |
运行 1 | 运行 2 |
| 1 | 0 | 0 | 0 | 目录未创建 | 13884 | 13897 |
| 2 | 0 | 0 | 0 | 0 | 13871 | 13818 |
| 3 | 0 | 0 | 1 | 0 | 13964 | 13924 |
| 4 | 0 | 0 | 0 | 1 | 13940 | 13939 |
| 5 | 1 | 1 | 0 | 0 | 13990 | 13993 |
| 6 | 0 | 0 | 0 | 2 (系统 + 用户) | 14063 | 14040 |
| 7 | 0 | 0 | 50 | 0 | 16011 | 16014 |
| 8 | 0 | 0 | 0 | 50 | 15456 | 15448 |
结果可以使用参考实现中提供的 run-benchmark.py 脚本重现 [6]。
我们从这些结果中解读出以下内容
- 使用两个
__sitecustomize__脚本与sitecustomize.py和usercustomize.py相比,解释器速度降低了 0.3%。我们预计这种减速将持续到sitecustomize.py和usercustomize.py在未来版本中被移除,因为即使用户不创建文件,解释器仍会尝试导入它们。 - 对于测试的包含代码的 50 个任意 pth 文件,将它们移动到
__sitecustomize__会使启动速度提高约 3.5%。这可能与评估__sitecustomize__文件比执行pth文件更简单的逻辑有关。 - 总的来说,所有测量结果都表明,这种添加对启动时间的影响很小。
审计事件
为了方便安全检查,将添加一个新的审计事件,并在执行 __sitecustomize__ 时触发,调用 sys.audit [12],名称为“sitecustomize.exec_file”,文件名为参数。
安全隐患
本 PEP 旨在将所有 pth 文件中的代码执行移至 __sitecustomize__ 目录中的文件。我们认为这对系统管理员来说是一个改进,原因如下:
- 允许通过查看单个目录,而不是扫描所有
pth文件,快速识别解释器在启动时执行的代码。 - 允许通过新提议的审计事件跟踪此功能的使用情况。
- 通过允许调整
__sitecustomize__目录的权限,提供更精细的控制,从而可能允许用户仅安装不改变解释器启动的包。
简而言之,虽然这允许恶意用户放置一个将在启动时执行的文件,但与现有的 pth 文件相比,这是一个改进。
如何教授
可以很简单地记录和教授这一点,只需说明解释器将在启动时在其站点路径中查找 __sitecustomize__ 目录,如果找到任何带有 .py 扩展名的文件,它将逐一执行。
对于系统管理员和打包解释器的工具,我们现在可以建议将文件放置在 __sitecustomize__ 中,就像他们以前放置 sitecustomize.py 一样。他们可以更放心地相信自己的内容不会被下一个人覆盖,因为他们可以提供特定的文件来处理他们想要自定义的逻辑。
库开发人员应该能够在 setuptools 等工具上指定一个新的参数,这将注入这些新文件。例如 sitecustomize_files=["scripts/betterexceptions.py"],这将允许他们添加这些文件。如果构建后端不支持,他们可以像以前处理 pth 文件一样手动安装它们。我们将建议他们将包的名称作为文件名的一部分。
向后兼容性
本 PEP 在 3.11、3.12 和 3.13 版本中,为 sitecustomize.py、usercustomize.py 和 pth 代码执行添加了弃用警告,并计划在 3.14 版本之前移除这些功能。从这些解决方案迁移到 __sitecustomize__ 理想情况下只需将逻辑移动到不同的文件。
虽然现有的 sitecustomize.py 机制是为系统管理员设计的,他们将其放置在站点路径中,但该文件实际上可以在解释器启动时的任何路径中放置。新机制不允许用户将 __sitecustomize__ 目录放置在路径中的任何位置,而只能放置在站点路径中。系统管理员可以通过在 __sitecustomize__ 中添加一个自定义文件来恢复与 sitecustomize.py 类似的行为,该文件只导入 sitecustomize 作为迁移路径。
参考实现
通过 CPython 测试套件的初步实现可供评估 [6]。
此实现仅供审阅者试用和检查本 PEP 可能产生的问题。
被拒绝的想法
不作为
虽然目前的状态“可以工作”,但它呈现了动机中列出的问题。在分析了此更改的影响之后,我们认为它值得,因为它带来了增强的用户体验。
正式使用 pth 文件
另一种选择是仅仅美化和文档化 pth 文件在启动时注入代码的使用,但正如动机中所述,这对用户来说是一种次优体验。
将 __sitecustomize__ 设为命名空间包
我们曾考虑将目录设为一个命名空间包,并仅导入其中的所有模块,这将允许在初始化时搜索 sys.path 中的所有路径,并提供了一种通过相互导入来声明文件之间依赖关系的方法。这被拒绝有多个原因
- 这不必要地扩大了执行任意文件的路径列表。
- 该逻辑带来了额外的复杂性,例如如果一个包要在其中一个位置安装
__init__.py文件该怎么办。 - 搜索
__sitecustomize__更便宜,因为我们已经在 site 路径中查找pth文件,而不是实际导入一个命名空间包。
支持关机自定义
init.d 用户可能会尝试以一种用户也可以在关机时添加代码的方式来实现此功能,但不需要额外的支持,因为 Python 用户已经可以通过 atexit 来实现。
使用 entry_points
我们考虑过扩展 entry points 的使用,以允许指定应在启动时执行的文件,但我们因两个主要原因放弃了该解决方案。第一个原因是启动时间的影响。这种方法需要扫描所有包分发信息才能执行少量文件。即使用户不使用该功能,这也会对性能产生影响,并且这种影响会随着环境中安装的包数量线性增长。第二个原因是本 PEP 中提议的实现为包和系统管理员提供了用于启动自定义的单一解决方案。此外,如果 entry points 的主要目标是让库更容易在启动时安装文件,那么仍然可以添加此功能,并让构建后端仅将文件安装到 __sitecustomize__ 目录中。
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
致谢
感谢 Pablo Galindo 对本 PEP 的贡献并提供他的 PC 运行基准测试。
参考资料
来源:https://github.com/python/peps/blob/main/peps/pep-0648.rst