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__()
函数。为了与内置导入功能以及通过导入引擎对象进行导入协同工作,本 PEP 提出了一种基于上下文管理的方法来临时替换全局导入状态。
本 PEP 还提出包含一个 GlobalImportEngine
子类以及该类的全局可访问实例,该实例“贯穿”到进程全局状态。这在提议的封装 API 和旧的进程全局状态之间提供了一个向后兼容的桥梁,并允许直接支持相关状态更新(例如,当修改 sys.path
时选择性地使路径缓存条目失效)。
PEP 撤回
自从最初编写本 PEP 以来,导入系统发生了重大变化,作为 Python 3.3 中的 PEP 420 和 Python 3.4 中的 PEP 451 的一部分。
虽然提供导入状态的封装仍然非常理想,但最好在新的 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())
隔离此状态将允许在进程中方便地存储多个导入状态。将导入功能放在一个自包含的对象中也将允许通过子类化添加其他功能(例如,模块导入通知或对可以导入哪些模块进行细粒度控制)。该引擎还将被子类化,以便可以使用导入引擎 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__()
可用于通过将__builtin__.__import__
替换为ImportEngine().__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)
提供对全局状态的类似引擎访问的便利类。提供__import__()
、import_module()
和from_engine()
方法,如ImportEngine
,但贯穿到sys
中的全局状态。
为了支持各种命名空间包机制,当修改 sys.path
时,应使用 pkgutil.extend_path
等工具修改导入状态的其他部分(在本例中为包 __path__
属性)。当进行各种更改时,也应使路径导入器缓存失效。
ImportEngine
API 将提供便利方法,这些方法会自动将相关导入状态更新作为单个操作的一部分进行。
全局变量
importlib.engine.sysengine
一个预先创建的GlobalImportEngine
实例。旨在供已更新为接受可选engine
参数的导入器和加载器以及使用ImportEngine.from_engine(sysengine)
以全局进程导入状态的副本开始使用。
查找器/加载器接口无变化
本 PEP 提出,ImportEngine
支持内容管理协议(类似于 decimal
模块中的上下文替换机制),而不是尝试更新 PEP 302 API 以接受其他状态。
ImportEngine
的上下文管理机制将
- 进入时:* 获取导入锁 * 使用导入引擎自己的状态替换全局导入状态
- 退出时:* 恢复先前的全局导入状态 * 释放导入锁
此 API 的精确设计尚待确定(但可能会使用一个不同的上下文管理对象,类似于 decimal.localcontext
创建的对象)。
未解决的问题
回退到全局导入状态的 API 设计
当前提案依赖于 from_engine()
API 回退到全局导入状态。可能需要提供一个变体,该变体改为动态回退到全局导入状态。
但是,从“尽可能隔离”的设计开始的一个巨大优势是,它可以尝试使用以各种方式模糊引擎实例状态和进程全局状态之间界限的子类。
内置模块和扩展模块必须是进程全局的
由于平台限制,每个进程中只能轻松存在每个内置模块和扩展模块的一个副本。因此,每个 ImportEngine
实例都无法独立加载此类模块。
最简单的解决方案是 ImportEngine
拒绝加载此类模块,并引发 ImportError
。 GlobalImportEngine
将能够正常加载它们。
ImportEngine
仍将从预填充的模块缓存中返回此类模块 - 只有直接加载它们才会导致问题。
替换范围
与前面的未解决问题相关的是,在使用上下文管理 API 时要替换哪个状态的问题。目前,替换 sys.modules
可能不可靠,因为存在缓存的引用,并且存在这样一个基本事实,即由于平台限制,某些模块不可能拥有独立的副本。
作为本 PEP 的一部分,需要明确记录
- 全局导入状态的哪些部分可以被替换(并声明缓存对该状态的引用的代码,而无需处理替换情况的错误)
- 哪些部分必须就地修改(因此不会被
ImportEngine
上下文管理 API 替换,或者以其他方式作用域到ImportEngine
实例)
参考实现
基于 Brett Cannon 的 importlib,Greg Slodkowicz 在 2011 年 Google Summer of Code 中为本 PEP 的早期草案开发了一个参考实现 [4]。请注意,当前的实现避免修改现有代码,因此不必要地重复了很多内容。实际的实现只会就地修改任何此类受影响的代码。
PEP 的早期草案建议更改 PEP 302 API 以支持传入一个可选的引擎实例。这有一个(严重)缺点,即无法正确影响从导入模块的进一步导入,因此更改为基于上下文管理的提案以替换全局状态。
参考文献
版权
本文档已放置在公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0406.rst