PEP 406 – 改进导入状态的封装
- 作者:
- Alyssa Coghlan <ncoghlan at gmail.com>, Greg Slodkowicz <jergosh at gmail.com>
- 状态:
- 已撤回
- 类型:
- 标准跟踪
- 创建日期:
- 2011年7月4日
- Python 版本:
- 3.4
- 发布历史:
- 2011年7月31日, 2011年11月13日, 2011年12月4日
摘要
此 PEP 提议在 importlib 中引入一个新的 ‘ImportEngine’ 类,它将封装所有与导入模块相关的状态到一个单一对象中。创建该对象的新实例将提供一种替代方法,用于完全替换内置的 import 语句实现,通过覆盖 __import__() 函数。为了与内置导入功能以及通过 import engine 对象导入配合工作,此 PEP 提议一种基于上下文管理的方案,用于临时替换全局导入状态。
该 PEP 还提议包含一个 GlobalImportEngine 子类和一个该类的全局可访问实例,它“写入”到进程全局状态。这为提议的封装 API 和遗留进程全局状态之间提供了向后兼容的桥梁,并允许对相关状态更新进行直接支持(例如,当 sys.path 被修改时,选择性地使路径缓存条目无效)。
PEP 撤回
导入系统自本 PEP 最初编写以来已经发生了实质性的变化,作为 Python 3.3 中 PEP 420 和 Python 3.4 中 PEP 451 的一部分。
虽然封装导入状态仍然非常理想,但最好通过一个新的 PEP 来解决,该 PEP 以 PEP 451 作为基础,并且只允许使用 PEP 451 兼容的查找器和加载器(因为它们避免了与旧加载器 API 相关的直接操作全局状态的许多问题)。
基本原理
目前,大多数与导入系统相关的状态都存储在 sys 模块的模块级别属性中。唯一的例外是导入锁,它不能直接访问,只能通过 imp 模块中的相关函数访问。当前的进程全局导入状态包括:
- sys.modules
- sys.path
- sys.path_hooks
- sys.meta_path
- sys.path_importer_cache
- 导入锁 (imp.lock_held()/acquire_lock()/release_lock())
隔离这些状态将允许在进程中方便地存储多个导入状态。将导入功能放在一个独立的对象中还将允许子类化以添加其他功能(例如,模块导入通知或对可以导入的模块进行精细控制)。引擎还将进行子类化,以便可以使用 import engine API 与现有的进程全局状态进行交互。
命名空间 PEP(尤其是 PEP 402)提出了对*其他*进程全局状态的潜在需求,以便在修改 sys.path 时正确更新包路径。
最后,为所有这些状态提供一个连贯的对象使得提供上下文管理功能成为可能,这些功能允许临时替换导入状态。
提案
我们提议引入一个 ImportEngine 类来封装导入功能。这包括一个 __import__() 方法,当需要时,可以作为内置 __import__() 的替代方法,还有一个 import_module() 方法,等同于 importlib.import_module() [3]。
由于存在需要维护的全局导入状态不变性,我们引入了一个 GlobalImportState 类,其接口与 ImportEngine 相同,但直接访问当前的全局导入状态。这可以使用类属性轻松实现。
规范
ImportEngine API
拟议的扩展包括以下对象:
importlib.engine.ImportEngine
from_engine(self, other)从另一个 ImportEngine 实例创建新的导入对象。新对象使用other中的状态副本进行初始化。当在importlib engine.sysengine上调用时,from_engine()可用于创建具有全局导入状态*副本*的ImportEngine对象。
__import__(self, name, globals={}, locals={}, fromlist=[], level=0)内置__import__()函数的重新实现。模块的导入将使用存储在 ImportEngine 实例中的状态,而不是全局导入状态。有关__import__功能的完整文档,请参阅 [2] 。来自ImportEngine及其子类的__import__()可以通过用ImportEngine().__import__替换__builtin__.__import__来自定义 import 语句的行为。
import_module(name, package=None)对importlib.import_module()的重新实现,它使用存储在 ImportEngine 实例中的导入状态。有关完整参考,请参阅 [3]。
modules, path, path_hooks, meta_path, path_importer_cache实例特定的对应于其进程全局sys等价物的版本。
importlib.engine.GlobalImportEngine(ImportEngine)
方便类,用于提供对全局状态的引擎式访问。提供像ImportEngine一样的__import__()、import_module()和from_engine()方法,但写入sys中的全局状态。
为了支持各种命名空间包机制,当 sys.path 被更改时,应该使用像 pkgutil.extend_path 这样的工具来修改导入状态的其他部分(在本例中是包的 __path__ 属性)。当进行各种更改时,还应使路径导入器缓存失效。
ImportEngine API 将提供便利方法,这些方法将在单个操作中自动进行相关的导入状态更新。
全局变量
importlib.engine.sysengine
GlobalImportEngine 的预创建实例。 intended for use by importers and loaders that have been updated to accept optionalengineparameters and withImportEngine.from_engine(sysengine)to start with a copy of the process global import state.
查找器/加载器接口无更改
此 PEP 提议,ImportEngine 支持上下文管理协议(类似于 decimal 模块中的上下文替换机制),而不是尝试修改 PEP 302 API 以接受其他状态。
ImportEngine 的上下文管理机制将:
- 进入时:* 获得导入锁 * 将全局导入状态替换为导入引擎自己的状态
- 退出时:* 恢复之前的全局导入状态 * 释放导入锁
此精确 API 待定(但可能会使用一个独立的上下文管理对象,类似于 decimal.localcontext 创建的对象)。
未解决的问题
回退到全局导入状态的API设计
当前的提议依赖于 from_engine() API 来回退到全局导入状态。可能希望提供一个变体,该变体动态地回退到全局导入状态。
然而,从“尽可能隔离”的设计开始的一个巨大优势是,可以尝试使用子类,这些子类以各种方式模糊了引擎实例状态和进程全局状态之间的界限。
内置模块和扩展模块必须是进程全局的
由于平台限制,每个进程中每个内置模块和扩展模块最多只能存在一个副本。因此,每个 ImportEngine 实例都无法独立加载这些模块。
最简单的解决方案是让 ImportEngine 拒绝加载这些模块,并引发 ImportError。 GlobalImportEngine 可以正常加载它们。
ImportEngine 仍将从预先填充的模块缓存中返回这些模块——只有直接加载它们才会引起问题。
替换范围
与前一个开放问题相关的是,在使用上下文管理 API 时应该替换什么状态。目前,替换 sys.modules 可能由于缓存的引用而不可靠,并且存在拥有某些模块独立副本根本不可能(由于平台限制)的基本事实。
作为此 PEP 的一部分,需要明确记录:
- 哪些全局导入状态部分可以被替换(并声明缓存该状态引用的代码,如果未处理替换情况,则可能存在 bug)
- 哪些部分必须就地修改(因此不会被
ImportEngine上下文管理 API 替换,或以其他方式限定到ImportEngine实例)
参考实现
Greg Slodkowicz 作为 2011 年 Google Summer of Code 的一部分,为本 PEP 的早期草案开发了一个参考实现 [4],该草案基于 Brett Cannon 的 importlib。请注意,当前实现避免修改现有代码,因此不必要地复制了许多内容。实际实现只会就地修改任何受影响的代码。
该 PEP 的早期草案提议修改 PEP 302 API 以支持传递一个可选的引擎实例。这有一个(严重的)缺点,即无法正确影响被导入模块的进一步导入,因此改变了基于上下文管理的方案来替换全局状态。
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0406.rst