5. 導(dǎo)入系統(tǒng)?

一個(gè) module 內(nèi)的 Python 代碼通過(guò) importing 操作就能夠訪(fǎng)問(wèn)另一個(gè)模塊內(nèi)的代碼。 import 語(yǔ)句是發(fā)起調(diào)用導(dǎo)入機(jī)制的最常用方式,但不是唯一的方式。 importlib.import_module() 以及內(nèi)置的 __import__() 等函數(shù)也可以被用來(lái)發(fā)起調(diào)用導(dǎo)入機(jī)制。

import 語(yǔ)句結(jié)合了兩個(gè)操作;它先搜索指定名稱(chēng)的模塊,然后將搜索結(jié)果綁定到當(dāng)前作用域中的名稱(chēng)。 import 語(yǔ)句的搜索操作被定義為對(duì) __import__() 函數(shù)的調(diào)用并帶有適當(dāng)?shù)膮?shù)。 __import__() 的返回值會(huì)被用于執(zhí)行 import 語(yǔ)句的名稱(chēng)綁定操作。 請(qǐng)參閱 import 語(yǔ)句了解名稱(chēng)綁定操作的更多細(xì)節(jié)。

對(duì) __import__() 的直接調(diào)用將僅執(zhí)行模塊搜索以及在找到時(shí)的模塊創(chuàng)建操作。 不過(guò)也可能產(chǎn)生某些副作用,例如導(dǎo)入父包和更新各種緩存 (包括 sys.modules),只有 import 語(yǔ)句會(huì)執(zhí)行名稱(chēng)綁定操作。

當(dāng) import 語(yǔ)句被執(zhí)行時(shí),標(biāo)準(zhǔn)的內(nèi)置 __import__() 函數(shù)會(huì)被調(diào)用。 其他發(fā)起調(diào)用導(dǎo)入系統(tǒng)的機(jī)制 (例如 importlib.import_module()) 可能會(huì)選擇繞過(guò) __import__() 并使用它們自己的解決方案來(lái)實(shí)現(xiàn)導(dǎo)入機(jī)制。

當(dāng)一個(gè)模塊首次被導(dǎo)入時(shí),Python 會(huì)搜索該模塊,如果找到就創(chuàng)建一個(gè) module 對(duì)象 1 并初始化它。 如果指定名稱(chēng)的模塊未找到,則會(huì)引發(fā) ModuleNotFoundError。 當(dāng)發(fā)起調(diào)用導(dǎo)入機(jī)制時(shí),Python 會(huì)實(shí)現(xiàn)多種策略來(lái)搜索指定名稱(chēng)的模塊。 這些策略可以通過(guò)使用使用下文所描述的多種鉤子來(lái)加以修改和擴(kuò)展。

在 3.3 版更改: 導(dǎo)入系統(tǒng)已被更新以完全實(shí)現(xiàn) PEP 302 中的第二階段要求。 不會(huì)再有任何隱式的導(dǎo)入機(jī)制 —— 整個(gè)導(dǎo)入系統(tǒng)都通過(guò) sys.meta_path 暴露出來(lái)。 此外,對(duì)原生命名空間包的支持也已被實(shí)現(xiàn) (參見(jiàn) PEP 420)。

5.1. importlib?

importlib 模塊提供了一個(gè)豐富的 API 用來(lái)與導(dǎo)入系統(tǒng)進(jìn)行交互。 例如 importlib.import_module() 提供了相比內(nèi)置的 __import__() 更推薦、更簡(jiǎn)單的 API 用來(lái)發(fā)起調(diào)用導(dǎo)入機(jī)制。 更多細(xì)節(jié)請(qǐng)參看 importlib 庫(kù)文檔。

5.2. ?

Python 只有一種模塊對(duì)象類(lèi)型,所有模塊都屬于該類(lèi)型,無(wú)論模塊是用 Python、C 還是別的語(yǔ)言實(shí)現(xiàn)。 為了幫助組織模塊并提供名稱(chēng)層次結(jié)構(gòu),Python 還引入了 的概念。

你可以把包看成是文件系統(tǒng)中的目錄,并把模塊看成是目錄中的文件,但請(qǐng)不要對(duì)這個(gè)類(lèi)比做過(guò)于字面的理解,因?yàn)榘湍K不是必須來(lái)自于文件系統(tǒng)。 為了方便理解本文檔,我們將繼續(xù)使用這種目錄和文件的類(lèi)比。 與文件系統(tǒng)一樣,包通過(guò)層次結(jié)構(gòu)進(jìn)行組織,在包之內(nèi)除了一般的模塊,還可以有子包。

要注意的一個(gè)重點(diǎn)概念是所有包都是模塊,但并非所有模塊都是包。 或者換句話(huà)說(shuō),包只是一種特殊的模塊。 特別地,任何具有 __path__ 屬性的模塊都會(huì)被當(dāng)作是包。

All modules have a name. Subpackage names are separated from their parent package name by a dot, akin to Python's standard attribute access syntax. Thus you might have a package called email, which in turn has a subpackage called email.mime and a module within that subpackage called email.mime.text.

5.2.1. 常規(guī)包?

Python 定義了兩種類(lèi)型的包,常規(guī)包命名空間包。 常規(guī)包是傳統(tǒng)的包類(lèi)型,它們?cè)?Python 3.2 及之前就已存在。 常規(guī)包通常以一個(gè)包含 __init__.py 文件的目錄形式實(shí)現(xiàn)。 當(dāng)一個(gè)常規(guī)包被導(dǎo)入時(shí),這個(gè) __init__.py 文件會(huì)隱式地被執(zhí)行,它所定義的對(duì)象會(huì)被綁定到該包命名空間中的名稱(chēng)。__init__.py 文件可以包含與任何其他模塊中所包含的 Python 代碼相似的代碼,Python 將在模塊被導(dǎo)入時(shí)為其添加額外的屬性。

例如,以下文件系統(tǒng)布局定義了一個(gè)最高層級(jí)的 parent 包和三個(gè)子包:

parent/
    __init__.py
    one/
        __init__.py
    two/
        __init__.py
    three/
        __init__.py

導(dǎo)入 parent.one 將隱式地執(zhí)行 parent/__init__.pyparent/one/__init__.py。 后續(xù)導(dǎo)入 parent.twoparent.three 則將分別執(zhí)行 parent/two/__init__.pyparent/three/__init__.py。

5.2.2. 命名空間包?

命名空間包是由多個(gè) 部分 構(gòu)成的,每個(gè)部分為父包增加一個(gè)子包。 各個(gè)部分可能處于文件系統(tǒng)的不同位置。 部分也可能處于 zip 文件中、網(wǎng)絡(luò)上,或者 Python 在導(dǎo)入期間可以搜索的其他地方。 命名空間包并不一定會(huì)直接對(duì)應(yīng)到文件系統(tǒng)中的對(duì)象;它們有可能是無(wú)實(shí)體表示的虛擬模塊。

命名空間包的 __path__ 屬性不使用普通的列表。 而是使用定制的可迭代類(lèi)型,如果其父包的路徑 (或者最高層級(jí)包的 sys.path) 發(fā)生改變,這種對(duì)象會(huì)在該包內(nèi)的下一次導(dǎo)入嘗試時(shí)自動(dòng)執(zhí)行新的對(duì)包部分的搜索。

命名空間包沒(méi)有 parent/__init__.py 文件。 實(shí)際上,在導(dǎo)入搜索期間可能找到多個(gè) parent 目錄,每個(gè)都由不同的部分所提供。 因此 parent/one 的物理位置不一定與 parent/two 相鄰。 在這種情況下,Python 將為頂級(jí)的 parent 包創(chuàng)建一個(gè)命名空間包,無(wú)論是它本身還是它的某個(gè)子包被導(dǎo)入。

另請(qǐng)參閱 PEP 420 了解對(duì)命名空間包的規(guī)格描述。

5.3. 搜索?

為了開(kāi)始搜索,Python 需要被導(dǎo)入模塊(或者包,對(duì)于當(dāng)前討論來(lái)說(shuō)兩者沒(méi)有差別)的完整 限定名稱(chēng)。 此名稱(chēng)可以來(lái)自 import 語(yǔ)句所帶的各種參數(shù),或者來(lái)自傳給 importlib.import_module()__import__() 函數(shù)的形參。

此名稱(chēng)會(huì)在導(dǎo)入搜索的各個(gè)階段被使用,它也可以是指向一個(gè)子模塊的帶點(diǎn)號(hào)路徑,例如 foo.bar.baz。 在這種情況下,Python 會(huì)先嘗試導(dǎo)入 foo,然后是 foo.bar,最后是 foo.bar.baz。 如果這些導(dǎo)入中的任何一個(gè)失敗,都會(huì)引發(fā) ModuleNotFoundError。

5.3.1. 模塊緩存?

在導(dǎo)入搜索期間首先會(huì)被檢查的地方是 sys.modules。 這個(gè)映射起到緩存之前導(dǎo)入的所有模塊的作用(包括其中間路徑)。 因此如果之前導(dǎo)入過(guò) foo.bar.baz,則 sys.modules 將包含 foo, foo.barfoo.bar.baz 條目。 每個(gè)鍵的值就是相應(yīng)的模塊對(duì)象。

在導(dǎo)入期間,會(huì)在 sys.modules 查找模塊名稱(chēng),如存在則其關(guān)聯(lián)的值就是需要導(dǎo)入的模塊,導(dǎo)入過(guò)程完成。 然而,如果值為 None,則會(huì)引發(fā) ModuleNotFoundError。 如果找不到指定模塊名稱(chēng),Python 將繼續(xù)搜索該模塊。

sys.modules 是可寫(xiě)的。刪除鍵可能不會(huì)破壞關(guān)聯(lián)的模塊(因?yàn)槠渌K可能會(huì)保留對(duì)它的引用),但它會(huì)使命名模塊的緩存條目無(wú)效,導(dǎo)致 Python 在下次導(dǎo)入時(shí)重新搜索命名模塊。鍵也可以賦值為 None ,強(qiáng)制下一次導(dǎo)入模塊導(dǎo)致 ModuleNotFoundError

但是要小心,因?yàn)槿绻氵€保有對(duì)某個(gè)模塊對(duì)象的引用,同時(shí)停用其在 sys.modules 中的緩存條目,然后又再次導(dǎo)入該名稱(chēng)的模塊,則前后兩個(gè)模塊對(duì)象將 不是 同一個(gè)。 相反地,importlib.reload() 將重用 同一個(gè) 模塊對(duì)象,并簡(jiǎn)單地通過(guò)重新運(yùn)行模塊的代碼來(lái)重新初始化模塊內(nèi)容。

5.3.2. 查找器和加載器?

如果指定名稱(chēng)的模塊在 sys.modules 找不到,則將發(fā)起調(diào)用 Python 的導(dǎo)入?yún)f(xié)議以查找和加載該模塊。 此協(xié)議由兩個(gè)概念性模塊構(gòu)成,即 查找器加載器。 查找器的任務(wù)是確定是否能使用其所知的策略找到該名稱(chēng)的模塊。 同時(shí)實(shí)現(xiàn)這兩種接口的對(duì)象稱(chēng)為 導(dǎo)入器 —— 它們?cè)诖_定能加載所需的模塊時(shí)會(huì)返回其自身。

Python 包含了多個(gè)默認(rèn)查找器和導(dǎo)入器。 第一個(gè)知道如何定位內(nèi)置模塊,第二個(gè)知道如何定位凍結(jié)模塊。 第三個(gè)默認(rèn)查找器會(huì)在 import path 中搜索模塊。 import path 是一個(gè)由文件系統(tǒng)路徑或 zip 文件組成的位置列表。 它還可以擴(kuò)展為搜索任意可定位資源,例如由 URL 指定的資源。

導(dǎo)入機(jī)制是可擴(kuò)展的,因此可以加入新的查找器以擴(kuò)展模塊搜索的范圍和作用域。

查找器并不真正加載模塊。 如果它們能找到指定名稱(chēng)的模塊,會(huì)返回一個(gè) 模塊規(guī)格說(shuō)明,這是對(duì)模塊導(dǎo)入相關(guān)信息的封裝,供后續(xù)導(dǎo)入機(jī)制用于在加載模塊時(shí)使用。

以下各節(jié)描述了有關(guān)查找器和加載器協(xié)議的更多細(xì)節(jié),包括你應(yīng)該如何創(chuàng)建并注冊(cè)新的此類(lèi)對(duì)象來(lái)擴(kuò)展導(dǎo)入機(jī)制。

在 3.4 版更改: 在之前的 Python 版本中,查找器會(huì)直接返回 加載器,現(xiàn)在它們則返回模塊規(guī)格說(shuō)明,其中 包含 加載器。 加載器仍然在導(dǎo)入期間被使用,但負(fù)擔(dān)的任務(wù)有所減少。

5.3.3. 導(dǎo)入鉤子?

導(dǎo)入機(jī)制被設(shè)計(jì)為可擴(kuò)展;其中的基本機(jī)制是 導(dǎo)入鉤子。 導(dǎo)入鉤子有兩種類(lèi)型: 元鉤子導(dǎo)入路徑鉤子。

元鉤子在導(dǎo)入過(guò)程開(kāi)始時(shí)被調(diào)用,此時(shí)任何其他導(dǎo)入過(guò)程尚未發(fā)生,但 sys.modules 緩存查找除外。 這允許元鉤子重載 sys.path 過(guò)程、凍結(jié)模塊甚至內(nèi)置模塊。 元鉤子的注冊(cè)是通過(guò)向 sys.meta_path 添加新的查找器對(duì)象,具體如下所述。

導(dǎo)入路徑鉤子是作為 sys.path (或 package.__path__) 過(guò)程的一部分,在遇到它們所關(guān)聯(lián)的路徑項(xiàng)的時(shí)候被調(diào)用。 導(dǎo)入路徑鉤子的注冊(cè)是通過(guò)向 sys.path_hooks 添加新的可調(diào)用對(duì)象,具體如下所述。

5.3.4. 元路徑?

當(dāng)指定名稱(chēng)的模塊在 sys.modules 中找不到時(shí),Python 會(huì)接著搜索 sys.meta_path,其中包含元路徑查找器對(duì)象列表。 這些查找器按順序被查詢(xún)以確定它們是否知道如何處理該名稱(chēng)的模塊。 元路徑查找器必須實(shí)現(xiàn)名為 find_spec() 的方法,該方法接受三個(gè)參數(shù):名稱(chēng)、導(dǎo)入路徑和目標(biāo)模塊(可選)。 元路徑查找器可使用任何策略來(lái)確定它是否能處理指定名稱(chēng)的模塊。

如果元路徑查找器知道如何處理指定名稱(chēng)的模塊,它將返回一個(gè)說(shuō)明對(duì)象。 如果它不能處理該名稱(chēng)的模塊,則會(huì)返回 None。 如果 sys.meta_path 處理過(guò)程到達(dá)列表末尾仍未返回說(shuō)明對(duì)象,則將引發(fā) ModuleNotFoundError。 任何其他被引發(fā)異常將直接向上傳播,并放棄導(dǎo)入過(guò)程。

元路徑查找器的 find_spec() 方法調(diào)用帶有兩到三個(gè)參數(shù)。 第一個(gè)是被導(dǎo)入模塊的完整限定名稱(chēng),例如 foo.bar.baz。 第二個(gè)參數(shù)是供模塊搜索使用的路徑條目。 對(duì)于最高層級(jí)模塊,第二個(gè)參數(shù)為 None,但對(duì)于子模塊或子包,第二個(gè)參數(shù)為父包 __path__ 屬性的值。 如果相應(yīng)的 __path__ 屬性無(wú)法訪(fǎng)問(wèn),將引發(fā) ModuleNotFoundError。 第三個(gè)參數(shù)是一個(gè)將被作為稍后加載目標(biāo)的現(xiàn)有模塊對(duì)象。 導(dǎo)入系統(tǒng)僅會(huì)在重加載期間傳入一個(gè)目標(biāo)模塊。

對(duì)于單個(gè)導(dǎo)入請(qǐng)求可以多次遍歷元路徑。 例如,假設(shè)所涉及的模塊都尚未被緩存,則導(dǎo)入 foo.bar.baz 將首先執(zhí)行頂級(jí)的導(dǎo)入,在每個(gè)元路徑查找器 (mpf) 上調(diào)用 mpf.find_spec("foo", None, None)。 在導(dǎo)入 foo 之后,foo.bar 將通過(guò)第二次遍歷元路徑來(lái)導(dǎo)入,調(diào)用 mpf.find_spec("foo.bar", foo.__path__, None)。 一旦 foo.bar 完成導(dǎo)入,最后一次遍歷將調(diào)用 mpf.find_spec("foo.bar.baz", foo.bar.__path__, None)

有些元路徑查找器只支持頂級(jí)導(dǎo)入。 當(dāng)把 None 以外的對(duì)象作為第三個(gè)參數(shù)傳入時(shí),這些導(dǎo)入器將總是返回 None。

Python 的默認(rèn) sys.meta_path 具有三種元路徑查找器,一種知道如何導(dǎo)入內(nèi)置模塊,一種知道如何導(dǎo)入凍結(jié)模塊,還有一種知道如何導(dǎo)入來(lái)自 import path 的模塊 (即 path based finder)。

在 3.4 版更改: 元路徑查找器的 find_spec() 方法替代了 find_module(),后者現(xiàn)已棄用,它將繼續(xù)可用但不會(huì)再做改變,導(dǎo)入機(jī)制僅會(huì)在查找器未實(shí)現(xiàn) find_spec() 時(shí)嘗試使用它。

在 3.10 版更改: 導(dǎo)入系統(tǒng)使用 find_module() 現(xiàn)在將會(huì)引發(fā) ImportWarning。

5.4. 加載?

當(dāng)一個(gè)模塊說(shuō)明被找到時(shí),導(dǎo)入機(jī)制將在加載該模塊時(shí)使用它(及其所包含的加載器)。 下面是導(dǎo)入的加載部分所發(fā)生過(guò)程的簡(jiǎn)要說(shuō)明:

module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
    # It is assumed 'exec_module' will also be defined on the loader.
    module = spec.loader.create_module(spec)
if module is None:
    module = ModuleType(spec.name)
# The import-related module attributes get set here:
_init_module_attrs(spec, module)

if spec.loader is None:
    # unsupported
    raise ImportError
if spec.origin is None and spec.submodule_search_locations is not None:
    # namespace package
    sys.modules[spec.name] = module
elif not hasattr(spec.loader, 'exec_module'):
    module = spec.loader.load_module(spec.name)
    # Set __loader__ and __package__ if missing.
else:
    sys.modules[spec.name] = module
    try:
        spec.loader.exec_module(module)
    except BaseException:
        try:
            del sys.modules[spec.name]
        except KeyError:
            pass
        raise
return sys.modules[spec.name]

請(qǐng)注意以下細(xì)節(jié):

  • 如果在 sys.modules 中存在指定名稱(chēng)的模塊對(duì)象,導(dǎo)入操作會(huì)已經(jīng)將其返回。

  • 在加載器執(zhí)行模塊代碼之前,該模塊將存在于 sys.modules 中。 這一點(diǎn)很關(guān)鍵,因?yàn)樵撃K代碼可能(直接或間接地)導(dǎo)入其自身;預(yù)先將其添加到 sys.modules 可防止在最壞情況下的無(wú)限遞歸和最好情況下的多次加載。

  • 如果加載失敗,則該模塊 -- 只限加載失敗的模塊 -- 將從 sys.modules 中移除。 任何已存在于 sys.modules 緩存的模塊,以及任何作為附帶影響被成功加載的模塊仍會(huì)保留在緩存中。 這與重新加載不同,后者會(huì)把即使加載失敗的模塊也保留在 sys.modules 中。

  • 在模塊創(chuàng)建完成但還未執(zhí)行之前,導(dǎo)入機(jī)制會(huì)設(shè)置導(dǎo)入相關(guān)模塊屬性(在上面的示例偽代碼中為 “_init_module_attrs”),詳情參見(jiàn) 后續(xù)部分。

  • 模塊執(zhí)行是加載的關(guān)鍵時(shí)刻,在此期間將填充模塊的命名空間。 執(zhí)行會(huì)完全委托給加載器,由加載器決定要填充的內(nèi)容和方式。

  • 在加載過(guò)程中創(chuàng)建并傳遞給 exec_module() 的模塊并不一定就是在導(dǎo)入結(jié)束時(shí)返回的模塊 2。

在 3.4 版更改: 導(dǎo)入系統(tǒng)已經(jīng)接管了加載器建立樣板的責(zé)任。 這些在以前是由 importlib.abc.Loader.load_module() 方法來(lái)執(zhí)行的。

5.4.1. 加載器?

模塊加載器提供關(guān)鍵的加載功能:模塊執(zhí)行。 導(dǎo)入機(jī)制調(diào)用 importlib.abc.Loader.exec_module() 方法并傳入一個(gè)參數(shù)來(lái)執(zhí)行模塊對(duì)象。 從 exec_module() 返回的任何值都將被忽略。

加載器必須滿(mǎn)足下列要求:

  • 如果模塊是一個(gè) Python 模塊(而非內(nèi)置模塊或動(dòng)態(tài)加載的擴(kuò)展),加載器應(yīng)該在模塊的全局命名空間 (module.__dict__) 中執(zhí)行模塊的代碼。

  • 如果加載器無(wú)法執(zhí)行指定模塊,它應(yīng)該引發(fā) ImportError,不過(guò)在 exec_module() 期間引發(fā)的任何其他異常也會(huì)被傳播。

在許多情況下,查找器和加載器可以是同一對(duì)象;在此情況下 find_spec() 方法將返回一個(gè)規(guī)格說(shuō)明,其中加載器會(huì)被設(shè)為 self。

模塊加載器可以選擇通過(guò)實(shí)現(xiàn) create_module() 方法在加載期間創(chuàng)建模塊對(duì)象。 它接受一個(gè)參數(shù),即模塊規(guī)格說(shuō)明,并返回新的模塊對(duì)象供加載期間使用。 create_module() 不需要在模塊對(duì)象上設(shè)置任何屬性。 如果模塊返回 None,導(dǎo)入機(jī)制將自行創(chuàng)建新模塊。

3.4 新版功能: 加載器的 create_module() 方法。

在 3.4 版更改: load_module() 方法被 exec_module() 所替代,導(dǎo)入機(jī)制會(huì)對(duì)加載的所有樣板責(zé)任作出假定。

為了與現(xiàn)有的加載器兼容,導(dǎo)入機(jī)制會(huì)使用導(dǎo)入器的 load_module() 方法,如果它存在且導(dǎo)入器也未實(shí)現(xiàn) exec_module()。 但是,load_module() 現(xiàn)已棄用,加載器應(yīng)該轉(zhuǎn)而實(shí)現(xiàn) exec_module()。

除了執(zhí)行模塊之外,load_module() 方法必須實(shí)現(xiàn)上文描述的所有樣板加載功能。 所有相同的限制仍然適用,并帶有一些附加規(guī)定:

  • 如果 sys.modules 中存在指定名稱(chēng)的模塊對(duì)象,加載器必須使用已存在的模塊。 (否則 importlib.reload() 將無(wú)法正確工作。) 如果該名稱(chēng)模塊不存在于 sys.modules 中,加載器必須創(chuàng)建一個(gè)新的模塊對(duì)象并將其加入 sys.modules。

  • 在加載器執(zhí)行模塊代碼之前,模塊 必須 存在于 sys.modules 之中,以防止無(wú)限遞歸或多次加載。

  • 如果加載失敗,加載器必須移除任何它已加入到 sys.modules 中的模塊,但它必須 僅限 移除加載失敗的模塊,且所移除的模塊應(yīng)為加載器自身顯式加載的。

在 3.5 版更改: 當(dāng) exec_module() 已定義但 create_module() 未定義時(shí)將引發(fā) DeprecationWarning

在 3.6 版更改: 當(dāng) exec_module() 已定義但 create_module() 未定義時(shí)將引發(fā) ImportError。

在 3.10 版更改: 使用 load_module() 將引發(fā) ImportWarning。

5.4.2. 子模塊?

當(dāng)使用任意機(jī)制 (例如 importlib API, importimport-from 語(yǔ)句或者內(nèi)置的 __import__()) 加載一個(gè)子模塊時(shí),父模塊的命名空間中會(huì)添加一個(gè)對(duì)子模塊對(duì)象的綁定。 例如,如果包 spam 有一個(gè)子模塊 foo,則在導(dǎo)入 spam.foo 之后,spam 將具有一個(gè) 綁定到相應(yīng)子模塊的 foo 屬性。 假如現(xiàn)在有如下的目錄結(jié)構(gòu):

spam/
    __init__.py
    foo.py

and spam/__init__.py has the following line in it:

from .foo import Foo

then executing the following puts name bindings for foo and Foo in the spam module:

>>>
>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.Foo
<class 'spam.foo.Foo'>

按照通常的 Python 名稱(chēng)綁定規(guī)則,這看起來(lái)可能會(huì)令人驚訝,但它實(shí)際上是導(dǎo)入系統(tǒng)的一個(gè)基本特性。 保持不變的一點(diǎn)是如果你有 sys.modules['spam']sys.modules['spam.foo'] (例如在上述導(dǎo)入之后就是如此),則后者必須顯示為前者的 foo 屬性。

5.4.3. 模塊規(guī)格說(shuō)明?

導(dǎo)入機(jī)制在導(dǎo)入期間會(huì)使用有關(guān)每個(gè)模塊的多種信息,特別是加載之前。 大多數(shù)信息都是所有模塊通用的。 模塊規(guī)格說(shuō)明的目的是基于每個(gè)模塊來(lái)封裝這些導(dǎo)入相關(guān)信息。

在導(dǎo)入期間使用規(guī)格說(shuō)明可允許狀態(tài)在導(dǎo)入系統(tǒng)各組件之間傳遞,例如在創(chuàng)建模塊規(guī)格說(shuō)明的查找器和執(zhí)行模塊的加載器之間。 最重要的一點(diǎn)是,它允許導(dǎo)入機(jī)制執(zhí)行加載的樣板操作,在沒(méi)有模塊規(guī)格說(shuō)明的情況下這是加載器的責(zé)任。

模塊的規(guī)格說(shuō)明會(huì)作為模塊對(duì)象的 __spec__ 屬性對(duì)外公開(kāi)。 有關(guān)模塊規(guī)格的詳細(xì)內(nèi)容請(qǐng)參閱 ModuleSpec。

3.4 新版功能.

5.4.5. module.__path__?

根據(jù)定義,如果一個(gè)模塊具有 __path__ 屬性,它就是包。

包的 __path__ 屬性會(huì)在導(dǎo)入其子包期間被使用。 在導(dǎo)入機(jī)制內(nèi)部,它的功能與 sys.path 基本相同,即在導(dǎo)入期間提供一個(gè)模塊搜索位置列表。 但是,__path__ 通常會(huì)比 sys.path 受到更多限制。

__path__ 必須是由字符串組成的可迭代對(duì)象,但它也可以為空。 作用于 sys.path 的規(guī)則同樣適用于包的 __path__,并且 sys.path_hooks (見(jiàn)下文) 會(huì)在遍歷包的 __path__ 時(shí)被查詢(xún)。

包的 __init__.py 文件可以設(shè)置或更改包的 __path__ 屬性,而且這是在 PEP 420 之前實(shí)現(xiàn)命名空間包的典型方式。 隨著 PEP 420 的引入,命名空間包不再需要提供僅包含 __path__ 操控代碼的 __init__.py 文件;導(dǎo)入機(jī)制會(huì)自動(dòng)為命名空間包正確地設(shè)置 __path__。

5.4.6. 模塊的 repr?

默認(rèn)情況下,全部模塊都具有一個(gè)可用的 repr,但是你可以依據(jù)上述的屬性設(shè)置,在模塊的規(guī)格說(shuō)明中更為顯式地控制模塊對(duì)象的 repr。

如果模塊具有 spec (__spec__),導(dǎo)入機(jī)制將嘗試用它來(lái)生成一個(gè) repr。 如果生成失敗或找不到 spec,導(dǎo)入系統(tǒng)將使用模塊中的各種可用信息來(lái)制作一個(gè)默認(rèn) repr。 它將嘗試使用 module.__name__, module.__file__ 以及 module.__loader__ 作為 repr 的輸入,并將任何丟失的信息賦為默認(rèn)值。

以下是所使用的確切規(guī)則:

  • 如果模塊具有 __spec__ 屬性,其中的規(guī)格信息會(huì)被用來(lái)生成 repr。 被查詢(xún)的屬性有 "name", "loader", "origin" 和 "has_location" 等等。

  • 如果模塊具有 __file__ 屬性,這會(huì)被用作模塊 repr 的一部分。

  • 如果模塊沒(méi)有 __file__ 但是有 __loader__ 且取值不為 None,則加載器的 repr 會(huì)被用作模塊 repr 的一部分。

  • 對(duì)于其他情況,僅在 repr 中使用模塊的 __name__。

在 3.4 版更改: loader.module_repr() 已棄用,導(dǎo)入機(jī)制現(xiàn)在使用模塊規(guī)格說(shuō)明來(lái)生成模塊 repr。

為了向后兼容 Python 3.3,如果加載器定義了 module_repr() 方法,則會(huì)在嘗試上述兩種方式之前先調(diào)用該方法來(lái)生成模塊 repr。 但請(qǐng)注意此方法已棄用。

在 3.10 版更改: 對(duì) module_repr() 的調(diào)用現(xiàn)在會(huì)在嘗試使用模塊的 __spec__ 屬性之后但在回退至 __file__ 之前發(fā)生。 module_repr() 的使用預(yù)定會(huì)在 Python 3.12 中停止。

5.4.7. 已緩存字節(jié)碼的失效?

在 Python 從 .pyc 文件加載已緩存字節(jié)碼之前,它會(huì)檢查緩存是否由最新的 .py 源文件所生成。 默認(rèn)情況下,Python 通過(guò)在所寫(xiě)入緩存文件中保存源文件的最新修改時(shí)間戳和大小來(lái)實(shí)現(xiàn)這一點(diǎn)。 在運(yùn)行時(shí),導(dǎo)入系統(tǒng)會(huì)通過(guò)比對(duì)緩存文件中保存的元數(shù)據(jù)和源文件的元數(shù)據(jù)確定該緩存的有效性。

Python 也支持“基于哈希的”緩存文件,即保存源文件內(nèi)容的哈希值而不是其元數(shù)據(jù)。 存在兩種基于哈希的 .pyc 文件:檢查型和非檢查型。 對(duì)于檢查型基于哈希的 .pyc 文件,Python 會(huì)通過(guò)求哈希源文件并將結(jié)果哈希值與緩存文件中的哈希值比對(duì)來(lái)確定緩存有效性。 如果檢查型基于哈希的緩存文件被確定為失效,Python 會(huì)重新生成并寫(xiě)入一個(gè)新的檢查型基于哈希的緩存文件。 對(duì)于非檢查型 .pyc 文件,只要其存在 Python 就會(huì)直接認(rèn)定緩存文件有效。 確定基于哈希的 .pyc 文件有效性的行為可通過(guò) --check-hash-based-pycs 旗標(biāo)來(lái)重載。

在 3.7 版更改: 增加了基于哈希的 .pyc 文件。在此之前,Python 只支持基于時(shí)間戳來(lái)確定字節(jié)碼緩存的有效性。

5.5. 基于路徑的查找器?

在之前已經(jīng)提及,Python 帶有幾種默認(rèn)的元路徑查找器。 其中之一是 path based finder (PathFinder),它會(huì)搜索包含一個(gè) 路徑條目 列表的 import path。 每個(gè)路徑條目指定一個(gè)用于搜索模塊的位置。

基于路徑的查找器自身并不知道如何進(jìn)行導(dǎo)入。 它只是遍歷單獨(dú)的路徑條目,將它們各自關(guān)聯(lián)到某個(gè)知道如何處理特定類(lèi)型路徑的路徑條目查找器。

默認(rèn)的路徑條目查找器集合實(shí)現(xiàn)了在文件系統(tǒng)中查找模塊的所有語(yǔ)義,可處理多種特殊文件類(lèi)型例如 Python 源碼 (.py 文件),Python 字節(jié)碼 (.pyc 文件) 以及共享庫(kù) (例如 .so 文件)。 在標(biāo)準(zhǔn)庫(kù)中 zipimport 模塊的支持下,默認(rèn)路徑條目查找器還能處理所有來(lái)自 zip 文件的上述文件類(lèi)型。

路徑條目不必僅限于文件系統(tǒng)位置。 它們可以指向 URL、數(shù)據(jù)庫(kù)查詢(xún)或可以用字符串指定的任何其他位置。

基于路徑的查找器還提供了額外的鉤子和協(xié)議以便能擴(kuò)展和定制可搜索路徑條目的類(lèi)型。 例如,如果你想要支持網(wǎng)絡(luò) URL 形式的路徑條目,你可以編寫(xiě)一個(gè)實(shí)現(xiàn) HTTP 語(yǔ)義在網(wǎng)絡(luò)上查找模塊的鉤子。 這個(gè)鉤子(可調(diào)用對(duì)象)應(yīng)當(dāng)返回一個(gè)支持下述協(xié)議的 path entry finder,以被用來(lái)獲取一個(gè)專(zhuān)門(mén)針對(duì)來(lái)自網(wǎng)絡(luò)的模塊的加載器。

預(yù)先的警告:本節(jié)和上節(jié)都使用了 查找器 這一術(shù)語(yǔ),并通過(guò) meta path finderpath entry finder 兩個(gè)術(shù)語(yǔ)來(lái)明確區(qū)分它們。 這兩種類(lèi)型的查找器非常相似,支持相似的協(xié)議,且在導(dǎo)入過(guò)程中以相似的方式運(yùn)作,但關(guān)鍵的一點(diǎn)是要記住它們是有微妙差異的。 特別地,元路徑查找器作用于導(dǎo)入過(guò)程的開(kāi)始,主要是啟動(dòng) sys.meta_path 遍歷。

相比之下,路徑條目查找器在某種意義上說(shuō)是基于路徑的查找器的實(shí)現(xiàn)細(xì)節(jié),實(shí)際上,如果需要從 sys.meta_path 移除基于路徑的查找器,并不會(huì)有任何路徑條目查找器被發(fā)起調(diào)用。

5.5.1. 路徑條目查找器?

path based finder 會(huì)負(fù)責(zé)查找和加載通過(guò) path entry 字符串來(lái)指定位置的 Python 模塊和包。 多數(shù)路徑條目所指定的是文件系統(tǒng)中的位置,但它們并不必受限于此。

作為一種元路徑查找器,path based finder 實(shí)現(xiàn)了上文描述的 find_spec() 協(xié)議,但是它還對(duì)外公開(kāi)了一些附加鉤子,可被用來(lái)定制模塊如何從 import path 查找和加載。

有三個(gè)變量由 path based finder, sys.path, sys.path_hookssys.path_importer_cache 所使用。 包對(duì)象的 __path__ 屬性也會(huì)被使用。 它們提供了可用于定制導(dǎo)入機(jī)制的額外方式。

sys.path 包含一個(gè)提供模塊和包搜索位置的字符串列表。 它初始化自 PYTHONPATH 環(huán)境變量以及多種其他特定安裝和實(shí)現(xiàn)的默認(rèn)設(shè)置。 sys.path 條目可指定的名稱(chēng)有文件系統(tǒng)中的目錄、zip 文件和其他可用于搜索模塊的潛在“位置”(參見(jiàn) site 模塊),例如 URL 或數(shù)據(jù)庫(kù)查詢(xún)等。 在 sys.path 中只能出現(xiàn)字符串和字節(jié)串;所有其他數(shù)據(jù)類(lèi)型都會(huì)被忽略。 字節(jié)串條目使用的編碼由單獨(dú)的 路徑條目查找器 來(lái)確定。

path based finder 是一種 meta path finder,因此導(dǎo)入機(jī)制會(huì)通過(guò)調(diào)用上文描述的基于路徑的查找器的 find_spec() 方法來(lái)啟動(dòng) import path 搜索。 當(dāng)要向 find_spec() 傳入 path 參數(shù)時(shí),它將是一個(gè)可遍歷的字符串列表 —— 通常為用來(lái)在其內(nèi)部進(jìn)行導(dǎo)入的包的 __path__ 屬性。 如果 path 參數(shù)為 None,這表示最高層級(jí)的導(dǎo)入,將會(huì)使用 sys.path。

基于路徑的查找器會(huì)迭代搜索路徑中的每個(gè)條目,并且每次都查找與路徑條目對(duì)應(yīng)的 path entry finder (PathEntryFinder)。 因?yàn)檫@種操作可能很耗費(fèi)資源(例如搜索會(huì)有 stat() 調(diào)用的開(kāi)銷(xiāo)),基于路徑的查找器會(huì)維持一個(gè)緩存來(lái)將路徑條目映射到路徑條目查找器。 這個(gè)緩存放于 sys.path_importer_cache (盡管如此命名,但這個(gè)緩存實(shí)際存放的是查找器對(duì)象而非僅限于 importer 對(duì)象)。 通過(guò)這種方式,對(duì)特定 path entry 位置的 path entry finder 的高耗費(fèi)搜索只需進(jìn)行一次。 用戶(hù)代碼可以自由地從 sys.path_importer_cache 移除緩存條目,以強(qiáng)制基于路徑的查找器再次執(zhí)行路徑條目搜索 3。

如果路徑條目不存在于緩存中,基于路徑的查找器會(huì)迭代 sys.path_hooks 中的每個(gè)可調(diào)用對(duì)象。 對(duì)此列表中的每個(gè) 路徑條目鉤子 的調(diào)用會(huì)帶有一個(gè)參數(shù),即要搜索的路徑條目。 每個(gè)可調(diào)用對(duì)象或是返回可處理路徑條目的 path entry finder,或是引發(fā) ImportError。 基于路徑的查找器使用 ImportError 來(lái)表示鉤子無(wú)法找到與 path entry 相對(duì)應(yīng)的 path entry finder。 該異常會(huì)被忽略并繼續(xù)進(jìn)行 import path 的迭代。 每個(gè)鉤子應(yīng)該期待接收一個(gè)字符串或字節(jié)串對(duì)象;字節(jié)串對(duì)象的編碼由鉤子決定(例如可以是文件系統(tǒng)使用的編碼 UTF-8 或其它編碼),如果鉤子無(wú)法解碼參數(shù),它應(yīng)該引發(fā) ImportError。

如果 sys.path_hooks 迭代結(jié)束時(shí)沒(méi)有返回 path entry finder,則基于路徑的查找器 find_spec() 方法將在 sys.path_importer_cache 中存入 None (表示此路徑條目沒(méi)有對(duì)應(yīng)的查找器) 并返回 None,表示此 meta path finder 無(wú)法找到該模塊。

如果 sys.path_hooks 中的某個(gè) path entry hook 可調(diào)用對(duì)象的返回值 一個(gè) path entry finder,則以下協(xié)議會(huì)被用來(lái)向查找器請(qǐng)求一個(gè)模塊的規(guī)格說(shuō)明,并在加載該模塊時(shí)被使用。

當(dāng)前工作目錄 -- 由一個(gè)空字符串表示 -- 的處理方式與 sys.path 中的其他條目略有不同。 首先,如果發(fā)現(xiàn)當(dāng)前工作目錄不存在,則 sys.path_importer_cache 中不會(huì)存放任何值。 其次,每個(gè)模塊查找會(huì)對(duì)當(dāng)前工作目錄的值進(jìn)行全新查找。 第三,由 sys.path_importer_cache 所使用并由 importlib.machinery.PathFinder.find_spec() 所返回的路徑將是實(shí)際的當(dāng)前工作目錄而非空字符串。

5.5.2. 路徑條目查找器協(xié)議?

為了支持模塊和已初始化包的導(dǎo)入,也為了給命名空間包提供組成部分,路徑條目查找器必須實(shí)現(xiàn) find_spec() 方法。

find_spec() 接受兩個(gè)參數(shù),即要導(dǎo)入模塊的完整限定名稱(chēng),以及(可選的)目標(biāo)模塊。 find_spec() 返回模塊的完全填充好的規(guī)格說(shuō)明。 這個(gè)規(guī)格說(shuō)明總是包含“加載器”集合(但有一個(gè)例外)。

為了向?qū)霗C(jī)制提示該規(guī)格說(shuō)明代表一個(gè)命名空間 portion,路徑條目查找器會(huì)將 "submodule_search_locations" 設(shè)為一個(gè)包含該部分的列表。

在 3.4 版更改: find_spec() 替代了 find_loader()find_module(),后兩者現(xiàn)在都已棄用,但會(huì)在 find_spec() 未定義時(shí)被使用。

較舊的路徑條目查找器可能會(huì)實(shí)現(xiàn)這兩個(gè)已棄用的方法中的一個(gè)而沒(méi)有實(shí)現(xiàn) find_spec()。 為保持向后兼容,這兩個(gè)方法仍會(huì)被接受。 但是,如果在路徑條目查找器上實(shí)現(xiàn)了 find_spec(),這兩個(gè)遺留方法就會(huì)被忽略。

find_loader() 接受一個(gè)參數(shù),即要導(dǎo)入模塊的完整限定名稱(chēng)。 find_loader() 返回一個(gè) 2 元組,其中第一項(xiàng)是加載器而第二項(xiàng)是命名空間 portion。

為了向后兼容其他導(dǎo)入?yún)f(xié)議的實(shí)現(xiàn),許多路徑條目查找器也同樣支持元路徑查找器所支持的傳統(tǒng) find_module() 方法。 但是路徑條目查找器 find_module() 方法的調(diào)用絕不會(huì)帶有 path 參數(shù)(它們被期望記錄來(lái)自對(duì)路徑鉤子初始調(diào)用的恰當(dāng)路徑信息)。

路徑條目查找器的 find_module() 方法已棄用,因?yàn)樗辉试S路徑條目查找器為命名空間包提供部分。 如果 find_loader()find_module() 同時(shí)存在于一個(gè)路徑條目查找器中,導(dǎo)入系統(tǒng)將總是調(diào)用 find_loader() 而不選擇 find_module()

在 3.10 版更改: 導(dǎo)入系統(tǒng)調(diào)用 find_module()find_loader() 將引發(fā) ImportWarning。

5.6. 替換標(biāo)準(zhǔn)導(dǎo)入系統(tǒng)?

替換整個(gè)導(dǎo)入系統(tǒng)的最可靠機(jī)制是移除 sys.meta_path 的默認(rèn)內(nèi)容,,將其完全替換為自定義的元路徑鉤子。

一個(gè)可行的方式是僅改變導(dǎo)入語(yǔ)句的行為而不影響訪(fǎng)問(wèn)導(dǎo)入系統(tǒng)的其他 API,那么替換內(nèi)置的 __import__() 函數(shù)可能就夠了。 這種技巧也可以在模塊層級(jí)上運(yùn)用,即只在某個(gè)模塊內(nèi)部改變導(dǎo)入語(yǔ)句的行為。

想要選擇性地預(yù)先防止在元路徑上從一個(gè)鉤子導(dǎo)入某些模塊(而不是完全禁用標(biāo)準(zhǔn)導(dǎo)入系統(tǒng)),只需直接從 find_spec() 引發(fā) ModuleNotFoundError 而非返回 None 就足夠了。 返回后者表示元路徑搜索應(yīng)當(dāng)繼續(xù),而引發(fā)異常則會(huì)立即終止搜索。

5.7. 包相對(duì)導(dǎo)入?

相對(duì)導(dǎo)入使用前綴點(diǎn)號(hào)。 一個(gè)前綴點(diǎn)號(hào)表示相對(duì)導(dǎo)入從當(dāng)前包開(kāi)始。 兩個(gè)或更多前綴點(diǎn)號(hào)表示對(duì)當(dāng)前包的上級(jí)包的相對(duì)導(dǎo)入,第一個(gè)點(diǎn)號(hào)之后的每個(gè)點(diǎn)號(hào)代表一級(jí)。 例如,給定以下的包布局結(jié)構(gòu):

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

不論是在 subpackage1/moduleX.py 還是 subpackage1/__init__.py 中,以下導(dǎo)入都是有效的:

from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo

絕對(duì)導(dǎo)入可以使用 import <>from <> import <> 語(yǔ)法,但相對(duì)導(dǎo)入只能使用第二種形式;其中的原因在于:

import XXX.YYY.ZZZ

應(yīng)當(dāng)提供 XXX.YYY.ZZZ 作為可用表達(dá)式,但 .moduleY 不是一個(gè)有效的表達(dá)式。

5.8. 有關(guān) __main__ 的特殊事項(xiàng)?

對(duì)于 Python 的導(dǎo)入系統(tǒng)來(lái)說(shuō) __main__ 模塊是一個(gè)特殊情況。 正如在 另一節(jié) 中所述,__main__ 模塊是在解釋器啟動(dòng)時(shí)直接初始化的,與 sysbuiltins 很類(lèi)似。 但是,與那兩者不同,它并不被嚴(yán)格歸類(lèi)為內(nèi)置模塊。 這是因?yàn)?__main__ 被初始化的方式依賴(lài)于發(fā)起調(diào)用解釋器所附帶的旗標(biāo)和其他選項(xiàng)。

5.8.1. __main__.__spec__?

根據(jù) __main__ 被初始化的方式,__main__.__spec__ 會(huì)被設(shè)置相應(yīng)值或是 None

當(dāng) Python 附加 -m 選項(xiàng)啟動(dòng)時(shí),__spec__ 會(huì)被設(shè)為相應(yīng)模塊或包的模塊規(guī)格說(shuō)明。 __spec__ 也會(huì)在 __main__ 模塊作為執(zhí)行某個(gè)目錄,zip 文件或其它 sys.path 條目的一部分加載時(shí)被填充。

其余的情況__main__.__spec__ 會(huì)被設(shè)為 None,因?yàn)橛糜谔畛?__main__ 的代碼不直接與可導(dǎo)入的模塊相對(duì)應(yīng):

  • 交互型提示

  • -c 選項(xiàng)

  • 從 stdin 運(yùn)行

  • 直接從源碼或字節(jié)碼文件運(yùn)行

請(qǐng)注意在最后一種情況中 __main__.__spec__ 總是為 None,即使 文件從技術(shù)上說(shuō)可以作為一個(gè)模塊被導(dǎo)入。 如果想要讓 __main__ 中的元數(shù)據(jù)生效,請(qǐng)使用 -m 開(kāi)關(guān)。

還要注意即使是在 __main__ 對(duì)應(yīng)于一個(gè)可導(dǎo)入模塊且 __main__.__spec__ 被相應(yīng)地設(shè)定時(shí),它們?nèi)詴?huì)被視為 不同的 模塊。 這是由于以下事實(shí):使用 if __name__ == "__main__": 檢測(cè)來(lái)保護(hù)的代碼塊僅會(huì)在模塊被用來(lái)填充 __main__ 命名空間時(shí)而非普通的導(dǎo)入時(shí)被執(zhí)行。

5.9. 開(kāi)放問(wèn)題項(xiàng)?

XXX 最好是能增加一個(gè)圖表。

XXX * (import_machinery.rst) 是否要專(zhuān)門(mén)增加一節(jié)來(lái)說(shuō)明模塊和包的屬性,也許可以擴(kuò)展或移植數(shù)據(jù)模型參考頁(yè)中的相關(guān)條目?

XXX 庫(kù)手冊(cè)中的 runpy 和 pkgutil 等等應(yīng)該都在頁(yè)面頂端增加指向新的導(dǎo)入系統(tǒng)章節(jié)的“另請(qǐng)參閱”鏈接。

XXX 是否要增加關(guān)于初始化 __main__ 的不同方式的更多解釋?zhuān)?/p>

XXX 增加更多有關(guān) __main__ 怪異/坑人特性的信息 (例如直接從 PEP 395 復(fù)制)。

5.10. 參考文獻(xiàn)?

導(dǎo)入機(jī)制自 Python 誕生之初至今已發(fā)生了很大的變化。 原始的 包規(guī)格說(shuō)明 仍然可以查閱,但在撰寫(xiě)該文檔之后許多相關(guān)細(xì)節(jié)已被修改。

原始的 sys.meta_path 規(guī)格說(shuō)明見(jiàn) PEP 302,后續(xù)的擴(kuò)展說(shuō)明見(jiàn) PEP 420。

PEP 420 為 Python 3.3 引入了 命名空間包。 PEP 420 還引入了 find_loader() 協(xié)議作為 find_module() 的替代。

PEP 366 描述了新增的 __package__ 屬性,用于在模塊中的顯式相對(duì)導(dǎo)入。

PEP 328 引入了絕對(duì)和顯式相對(duì)導(dǎo)入,并初次提出了 __name__ 語(yǔ)義,最終由 PEP 366__package__ 加入規(guī)范描述。

PEP 338 定義了將模塊作為腳本執(zhí)行。

PEP 451 在 spec 對(duì)象中增加了對(duì)每個(gè)模塊導(dǎo)入狀態(tài)的封裝。 它還將加載器的大部分樣板責(zé)任移交回導(dǎo)入機(jī)制中。 這些改變?cè)试S棄用導(dǎo)入系統(tǒng)中的一些 API 并為查找器和加載器增加一些新的方法。

備注

1

參見(jiàn) types.ModuleType。

2

importlib 實(shí)現(xiàn)避免直接使用返回值。 而是通過(guò)在 sys.modules 中查找模塊名稱(chēng)來(lái)獲取模塊對(duì)象。 這種方式的間接影響是被導(dǎo)入的模塊可能在 sys.modules 中替換其自身。 這屬于具體實(shí)現(xiàn)的特定行為,不保證能在其他 Python 實(shí)現(xiàn)中起作用。

3

在遺留代碼中,有可能在 sys.path_importer_cache 中找到 imp.NullImporter 的實(shí)例。 建議將這些代碼修改為使用 None 代替。 詳情參見(jiàn) Porting Python code。