在Python中,有兩種默認的元路徑查找器:基于路徑的查找器(PathFinder)和源文件查找器(SourceFileFinder)。基于路徑的查找器會搜索包含一個路徑條目列表的import path,每個路徑條目指定一個用于搜索模塊的位置。
然而,基于路徑的查找器本身并不知道如何進行導入。它只是遍歷單獨的路徑條目,將它們各自關聯(lián)到某個知道如何處理特定類型路徑的路徑條目查找器。
默認的路徑條目查找器集合實現(xiàn)了在文件系統(tǒng)中查找模塊的所有語義,可以處理多種特殊文件類型,如Python源碼(.py文件)、Python字節(jié)碼(.pyc文件)和共享庫(如.so文件)。在標準庫中,zipimport模塊的支持使得默認路徑條目查找器還能處理來自zip文件的所有上述文件類型。
路徑條目不必僅限于文件系統(tǒng)位置,它們可以指向URL、數(shù)據(jù)庫查詢或任何其他可以用字符串指定的位置?;诼窂降牟檎移鬟€提供了額外的鉤子和協(xié)議以便能擴展和定制可搜索路徑條目的類型。例如,如果你想要支持網(wǎng)絡URL形式的路徑條目,你可以編寫一個實現(xiàn)HTTP語義在網(wǎng)絡上查找模塊的鉤子。這個鉤子(可調(diào)用對象)應當返回一個支持特定協(xié)議的路徑條目查找器,以被用來獲取一個專門針對來自網(wǎng)絡的模塊的加載器。
注意:本篇教程使用了查找器這一術語,并通過 meta path finder 和 path entry finder 兩個術語來明確區(qū)分它們。 這兩種類型的查找器非常相似,支持相似的協(xié)議,且在導入過程中以相似的方式運作,但關鍵的一點是要記住它們是有微妙差異的。 元路徑查找器作用于導入過程的開始,主要是啟動 sys.meta_path 遍歷。
相比之下,路徑條目查找器在某種意義上說是基于路徑的查找器的實現(xiàn)細節(jié),實際上,如果需要從 sys.meta_path 移除基于路徑的查找器,并不會有任何路徑條目查找器被發(fā)起調(diào)用。
一、路徑條目查找器
path based finder會負責查找和加載通過 path entry 字符串來指定位置的 Python 模塊和包。 多數(shù)路徑條目所指定的是文件系統(tǒng)中的位置,但它們并不必受限于此。作為一種元路徑查找器,基于路徑的查找器實現(xiàn)了上文描述的find_spec()協(xié)議,但是它還對外公開了一些附加鉤子,可被用來定制模塊如何從import path查找和加載。
有三個變量由基于路徑的查找器、sys.path、sys.path_hooks和sys.path_importer_cache所使用。包對象的__path__屬性也會被使用。它們提供了可用于定制導入機制的額外方式。
sys.path 包含一個提供模塊和包搜索位置的字符串列表, 它初始化自 PYTHONPATH 環(huán)境變量以及多種其他特定安裝和實現(xiàn)專屬的默認位置。 sys.path 中的條目可指定文件系統(tǒng)中的目錄、zip 文件及其他可用于搜索模塊的潛在 “位置” ,例如 URL 或數(shù)據(jù)庫查詢等。 在 sys.path 中應當只有字符串;所有其他類數(shù)據(jù)類型都會被忽略。
path based finder 是一種 meta path finder,因此導入機制會通過調(diào)用上文描述的基于路徑的查找器的 find_spec() 方法來啟動 import path 搜索。 當要向 find_spec() 傳入 path 參數(shù)時,它將是一個可遍歷的字符串列表 —— 通常為用來在其內(nèi)部進行導入的包的 __path__ 屬性。 如果 path 參數(shù)為 None,這表示最高層級的導入,將會使用 sys.path。
如果 sys.path_hooks 迭代結(jié)束時沒有返回 path entry finder,則基于路徑的如果 sys.path_hooks 迭代結(jié)束時沒有返回 path entry finder,則基于路徑的查找器 find_spec() 方法將在 sys.path_importer_cache 中存入 None(表示此路徑條目沒有對應的查找器),并返回 None,表示此 meta path finder 無法找到該模塊。
如果 sys.path_hooks 中的某個 path entry hook 可調(diào)用對象的返回值是一個 path entry finder,則以下協(xié)議會被用來向查找器請求一個模塊的規(guī)格說明,并在加載該模塊時被使用。
當前工作目錄的處理方式與 sys.path 中的其他條目略有不同。首先,如果發(fā)現(xiàn)當前工作目錄不存在,則 sys.path_importer_cache 中不會存放任何值。其次,每個模塊查找會對當前工作目錄的值進行全新查找。第三,由 sys.path_importer_cache 所使用并由 importlib.machinery.PathFinder.find_spec() 所返回的路徑將是實際的當前工作目錄而非空字符串
二、路徑條目查找器協(xié)議
為了支持模塊和已初始化包的導入,也為了給命名空間包提供組成部分,路徑條目查找器必須實現(xiàn) find_spec() 方法。find_spec() 接受兩個參數(shù),即要導入模塊的完整限定名稱,以及(可選的)目標模塊。 find_spec() 返回模塊的完全填充好的規(guī)格說明。 這個規(guī)格說明總是包含“加載器”集合(但有一個例外)。
為了向?qū)霗C制提示該規(guī)格說明代表一個命名空間 portion,路徑條目查找器會將 submodule_search_locations 設為一個包含該部分的列表。
較舊的路徑條目查找器可能會實現(xiàn)這兩個已棄用的方法中的一個而沒有實現(xiàn) find_spec()。 為保持向后兼容,這兩個方法仍會被接受。 但是,如果在路徑條目查找器上實現(xiàn)了 find_spec(),這兩個遺留方法就會被忽略。
find_loader() 接受一個參數(shù),即要導入模塊的完整限定名稱。 find_loader() 返回一個 2 元組,其中第一項是加載器而第二項是命名空間 portion。為了向后兼容其他導入?yún)f(xié)議的實現(xiàn),許多路徑條目查找器也同樣支持元路徑查找器所支持的傳統(tǒng) find_module() 方法。 但是路徑條目查找器 find_module() 方法的調(diào)用絕不會帶有 path 參數(shù)(它們被期望記錄來自對路徑鉤子初始調(diào)用的恰當路徑信息)。
路徑條目查找器的 find_module() 方法已被棄用,因為它不允許路徑條目查找器為命名空間包提供部分。如果 find_loader() 和 find_module() 同時存在于一個路徑條目查找器中,導入系統(tǒng)將總是調(diào)用 find_loader() 而不選擇 find_module()。