PEP 687 – 隔离标准库中的模块
- 作者:
- Erlend Egeberg Aasland <erlend at python.org>, Petr Viktorin <encukou at gmail.com>
- 讨论列表:
- Discourse 帖子
- 状态:
- 已接受
- 类型:
- 标准跟踪
- 依赖:
- 489, 573, 630
- 创建日期:
- 2022年4月4日
- Python 版本:
- 3.12
- 历史记录:
- 2022年4月4日, 2022年4月11日
- 决议:
- Discourse 消息
摘要
标准库中的扩展将转换为多阶段初始化(PEP 489),并且在可能的情况下,所有状态都将存储在模块对象上,而不是存储在进程全局变量中。
关于回溯的说明
该提案的许多内容已经实现。我们提交此 PEP 以解释这些更改,寻求关于它们是否良好的共识,提出剩余的更改,并为新模块设定最佳实践。
动机与理由
信息性的 PEP 630 描述了所提议更改的背景、动机、理由、影响和实现说明,这些更改通常适用于任何扩展模块(不仅仅是标准库)。
它是本提案的组成部分。请先阅读。
本 PEP 讨论标准库的细节。
规范
PEP 630 的主体将转换为 Python 文档中的 HOWTO,并且该 PEP 将被废弃(标记为最终)。
标准库中的所有扩展模块都将转换为在 PEP 489 中引入的多阶段初始化。
所有标准库扩展模块都将被 *隔离*。也就是说
- 模块定义的类型、函数和其他对象要么是不可变的,要么不与其他模块实例共享。
- 特定于模块的状态不会与其他模块实例共享,除非它表示全局状态。
例如,
_csv.field_size_limit
将获取/设置特定于模块的数字。另一方面,诸如readline.get_history_item
或os.getpid
之类的函数将继续使用进程全局的状态(模块外部,并且可能与其他库共享,包括非 Python 库)。
转换为堆类型
不需要模块状态访问且没有其他转换理由的静态类型应保持静态。
其方法需要访问其模块实例的类型将根据 PEP 630 转换为堆类型,并考虑以下事项
- 所有以前是静态类型的标准库类型都应保持不可变。堆类型必须使用
Py_TPFLAGS_IMMUTABLE_TYPE
标志定义以保留不可变性。请参阅 bpo-43908。测试应确保在尝试创建不可变类型的新的属性时引发
TypeError
。 - 具有
tp_new = NULL
的静态类型没有公共构造函数,但堆类型继承基类的构造函数。确保以前无法实例化的类型保留该特性;使用Py_TPFLAGS_DISALLOW_INSTANTIATION
。使用test.support.check_disallow_instantiation()
添加测试。请参阅 bpo-43916。 - 转换后的堆类型可能会无意中变得可序列化(
pickle
可用)。测试调用pickle.dumps
在转换前后是否具有相同的结果,如果测试失败,则添加一个__reduce__
方法,该方法引发TypeError
。请参阅 PR-21002 以获取示例。
这些问题将添加到 Devguide 中,以帮助任何未来的转换。
如果发现其他类型的问题,则相关模块应保持不变,直到找到解决方案并将其添加到 Devguide 中,并且已转换的模块已检查并修复。
流程
以下流程应添加到 Devguide 中,并在所有模块转换完成之前保持不变。任何新的发现都应记录在其中或通用 HOWTO 中。
第1部分:准备
- 开启讨论,无论是在错误跟踪器上还是在 Discourse 上。让模块维护者和/或代码所有者参与进来。解释更改的原因和理由。
- 识别全局状态性能瓶颈。创建概念验证实现,并衡量性能影响。
pyperf
是一个用于基准测试的好工具。 - 创建实施计划。对于类型较少的较小模块,单个 PR 可能可以完成工作。对于类型较多且可能还有外部库回调的较大模块,将需要多个 PR。
第2部分:实现
注意:这是基于从其他模块中吸取的经验教训,针对复杂模块提出的建议实施计划。对于较小的模块,请随意简化它。
- 在可能的情况下添加 Argument Clinic;它使您可以轻松地使用定义类从类型方法中获取模块状态。
- 为模块状态做准备;建立模块状态
struct
,添加一个实例作为静态全局变量,并创建用于获取模块状态的帮助程序存根。 - 将相关全局变量添加到模块状态
struct
中,并将访问全局状态的代码修改为使用模块状态帮助程序。此步骤可能被分解为多个 PR。 - 在必要时,将静态类型转换为堆类型。
- 将全局模块状态结构转换为真正的模块状态。
- 实现多阶段初始化。
步骤 4 到 6 最好在一个 alpha 开发阶段完成。
向后兼容性
标准库中的扩展模块现在可以加载多次。例如,从 sys.modules
中删除此类模块并重新导入它将导致一个新的模块实例,与任何先前加载的实例隔离。
这可能会影响预期先前行为的代码:扩展模块的全局变量是从第一个加载的模块浅复制的。
安全影响
未知。
如何教授
该提案的很大一部分是针对经验丰富的用户的 HOWTO,它将被移至文档中。
初学者不应受到影响。
参考实现
大多数更改现在都在主分支中,作为这些问题的提交
- bpo-40077,将静态类型转换为堆类型:使用 PyType_FromSpec()
- bpo-46417,在 Py_Finalize() 中清除嵌入式 Python 的静态类型
- bpo-1635741,Py_Finalize() 在退出时不会清除所有 Python 对象
例如,在 _csv
模块中完成的更改和修复是
版权
本文件置于公共领域或根据 CC0-1.0-Universal 许可证发布,以两者中许可范围更宽者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0687.rst
上次修改时间:2023-10-04 23:18:07 GMT