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
文件(在启动时加载并在站点模块[7]中实现)可以包含在评估 pth
文件时将执行的 Python 代码。
请注意,pth
文件最初开发的目的是将其他目录添加到 sys.path
中,但它们还可以包含以“import”开头的行,这些行将传递给 exec()
。用户利用此功能来允许他们所需的自定义。请参阅 setuptools [4] 或 betterexceptions [5] 作为示例。
出于此目的使用 pth
文件对于库开发人员来说远非理想,因为他们需要将代码注入到以导入开头的单行中,这使得代码难以阅读。遵循该实践的库开发人员通常会创建一个在导入时执行所有操作的模块,如 betterexceptions [5] 所做的那样,但这种方法仍然不是真正用户友好的。
此外,对于解释器的用户来说,如果他们想检查在 Python 启动时正在执行什么内容,这也不是理想的选择,因为他们需要查看所有 pth
文件以查找可能分布在所有站点路径中的潜在代码执行。大多数这些 pth
文件将是“合法”的 pth
文件,它们只会修改路径,从而使得回答“是什么在启动时更改我的解释器”的问题变得相当复杂。
sitecustomize.py
的限制
虽然 sitecustomize 是一个可接受的解决方案,但它假设只有一人负责系统和解释器。如果系统管理员和解释器供应的责任方都希望在解释器启动时添加自定义,则他们需要就文件的内容达成一致并合并所有更改。不过,这不是一个主要限制,也不是此更改的主要驱动因素。如果更改发生,它也将改善这些用户的状况,因为他们可以拥有以他们想要增强的功能命名的自定义隔离文件,而不是拥有执行所有这些操作的 sitecustomize.py
。例如,Ubuntu 可以将其当前的 sitecustomize.py
更改为仅为 ubuntu_apport_python_hook
。这不仅更好地体现了其意图,而且使解释器的用户能够更好地理解其解释器上发生的修改。
基本原理
本 PEP 提出支持在启动时通过执行在 sitepackages [8] 或 usersitepackages [9] 中名为 __sitecustomize__
的目录中发现的所有文件来扩展解释器的自定义。
为什么使用 __sitecustomize__
该名称旨在遵循已存在的 sitecustomize.py
概念。由于该目录将在 sys.path
内,因为它位于站点路径中,因此我们选择在其名称周围使用双下划线,以防止与已存在的 sitecustomize.py
冲突。
发现新的 __sitecustomize__
目录
Python 解释器将在启动时查找任何标准站点包路径中名为 __sitecustomize__
的目录。
这些通常是 Python 系统位置和用户位置,但最终由站点模块逻辑定义。
用户可以使用 site.sitepackages
[8] 和 site.usersitepackages
[9] 来了解解释器可以发现 __sitecustomize__
目录的路径。
__sitecustomize__
发现时间
__sitecustomize__
目录将在 pth
文件作为 site.addsitedir
[10] 的一部分在站点包路径中被发现后立即被发现。
对于每个站点包路径,都将重复此操作,并且按照今天对 pth
文件所遵循的完全相同的顺序进行。
__sitecustomize__
内部的执行顺序
该实现将通过在发现每个 __sitecustomize__
目录时按名称对它们进行排序来执行 __sitecustomize__
内的文件。不过,我们不鼓励用户依赖执行顺序。
我们考虑以随机顺序执行它们,但这可能会导致不同的结果,具体取决于解释器如何选择获取这些文件。因此,即使依靠其他文件的执行不是一个好习惯,我们认为这比在解释器启动时获得随机不同的结果要好。我们选择在 pth
文件之后运行这些文件,以防用户需要在运行文件之前将项目添加到路径中。
与 pth
文件的交互
pth
文件可用于将路径添加到 sys.path
中,但这不应影响 __sitecustomize__
的发现过程,因为这些目录仅在站点包路径中查找。
__sitecustomize__
内文件的执行
当发现 __sitecustomize__
目录时,其中所有具有 .py
扩展名的文件都将使用 io.open_code
读取,并使用 exec
[11] 执行。
将一个空字典作为 globals
传递给 exec
函数,以防止不同文件之间发生意外交互。
错误处理
除非解释器在详细模式下运行,否则任何文件的执行错误都不会记录,并且不应停止其他文件的评估。用户将在 stderr 中收到一条消息,提示文件未能执行,并且可以使用详细模式获取更多信息。此行为模仿了 sitecustomize.py
中存在的行为。
与虚拟环境的交互
当用户创建虚拟环境时,通过新的 __sitecustomize__
解决方案应用于解释器的自定义将继续起作用,这与 sitecustomize.py
与虚拟环境交互的方式相同。
这与 pth
文件相比有所不同,除非启用了 include-system-site-packages
,否则 pth
文件不会传播到虚拟环境中。
如果库维护者通过 __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
文件中的代码影响启动时间。因此,我们将重点关注将此解决方案与这三个解决方案进行比较,否则添加到启动的实际时间高度依赖于在这些文件中执行的代码。
结果是通过在 50 次迭代中运行“./python.exe -c pass”以及 perf 来收集的,在每次迭代中重复 50 次命令并获取所有结果的几何平均值。用于运行这些基准测试的文件已在参考实现中签入 [6]。
基准测试使用 3.10 alpha 7 编译,使用 PGO 和 LTO,并使用以下参数和系统状态
- Perf 事件:最大采样率设置为每秒 1 次
- CPU 频率:CPU 17、35 的最低频率设置为最高频率
- Turbo Boost(MSR):在 CPU 17 上禁用 Turbo Boost: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
- Perf 事件:最大采样率:每秒 1 次
- ASLR:完全随机化
- Linux 调度程序:隔离 CPU(2/36):17、35
- Linux 调度程序:在 CPU(2/36)上禁用 RCU:17、35
- CPU 频率:0-16、18-34=最小=1200 MHz,最大=3600 MHz;17、35=最小=最大=3600 MHz
- Turbo Boost(MSR):CPU 17、35:已禁用
放置在 pth
文件、sitecustomize.py
、usercustomize.py
和 __sitecustomize__
中的文件中要执行的代码如下
import time; x = time.time() ** 5
该文件旨在执行一个简单的操作,但仍预计可以忽略不计。这是为了将实验置于一种情况下,在这种情况下,我们使由于该机制导致的任何性能下降变得可见,同时使其保持相对真实。此外,它以导入开头,并且是一行代码,以便能够在 pth
文件中使用。
测试 | 文件数量 | 时间(us) | ||||
---|---|---|---|---|---|---|
# | 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](名称为“sitecustimze.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__
的成本更低,因为我们已经在站点路径中查找pth
文件,而这比实际导入命名空间包要便宜。
支持关闭自定义
init.d
用户可能会尝试以一种允许用户在关闭时添加代码的方式实现此功能,但不需要为此提供额外的支持,因为 Python 用户已经可以通过atexit
做到这一点。
使用 entry_points
我们考虑过扩展入口点的使用,以允许指定在启动时应执行的文件,但由于两个主要原因放弃了该方案。第一个是启动时间的影响。这种方法需要扫描所有包的发布信息,以仅执行少量文件。即使用户没有使用此功能,也会对性能产生影响,并且这种影响会随着环境中安装的包数量线性增长。第二个原因是,本 PEP 中提出的实现为包和系统管理员提供了启动自定义的单一解决方案。此外,如果入口点的主要目标是让库能够轻松地在启动时安装文件,那么仍然可以添加此功能,并使构建后端仅将文件安装在__sitecustomize__
目录中。
版权
本文档置于公有领域或根据 CC0-1.0-Universal 许可证发布,以较宽松者为准。
致谢
感谢 Pablo Galindo 为本 PEP 做出的贡献,以及提供他的电脑进行基准测试。
参考文献
来源:https://github.com/python/peps/blob/main/peps/pep-0648.rst
最后修改时间:2023-09-09 17:39:29 GMT