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