日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網營銷解決方案
創(chuàng)新互聯(lián)Python教程:5.導入系統(tǒng)

5. 導入系統(tǒng)

一個 module 內的 python 代碼通過 importing 操作就能夠訪問另一個模塊內的代碼。 import 語句是發(fā)起調用導入機制的最常用方式,但不是唯一的方式。 importlib.import_module() 以及內置的 __import__() 等函數(shù)也可以被用來發(fā)起調用導入機制。

創(chuàng)新互聯(lián)公司主營庫車網站建設的網絡公司,主營網站建設方案,成都App定制開發(fā),庫車h5重慶小程序開發(fā)公司搭建,庫車網站營銷推廣歡迎庫車等地區(qū)企業(yè)咨詢

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

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

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

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

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

5.1. importlib

importlib 模塊提供了一個豐富的 API 用來與導入系統(tǒng)進行交互。 例如 importlib.import_module() 提供了相比內置的 __import__() 更推薦、更簡單的 API 用來發(fā)起調用導入機制。 更多細節(jié)請參看 importlib 庫文檔。

5.2. 包

Python 只有一種模塊對象類型,所有模塊都屬于該類型,無論模塊是用 Python、C 還是別的語言實現(xiàn)。 為了幫助組織模塊并提供名稱層次結構,Python 還引入了 包 的概念。

你可以把包看成是文件系統(tǒng)中的目錄,并把模塊看成是目錄中的文件,但請不要對這個類比做過于字面的理解,因為包和模塊不是必須來自于文件系統(tǒng)。 為了方便理解本文檔,我們將繼續(xù)使用這種目錄和文件的類比。 與文件系統(tǒng)一樣,包通過層次結構進行組織,在包之內除了一般的模塊,還可以有子包。

要注意的一個重點概念是所有包都是模塊,但并非所有模塊都是包。 或者換句話說,包只是一種特殊的模塊。 特別地,任何具有 __path__ 屬性的模塊都會被當作是包。

所有模塊都有自己的名字。 子包名與其父包名會以點號分隔,與 Python 的標準屬性訪問語法一致。 因此你可能會有一個名為 email 的包,這個包中又有一個名為 email.mime 的子包以及該子包中的名為 email.mime.text 的子包。

5.2.1. 常規(guī)包

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

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

 
 
 
 
  1. parent/
  2. __init__.py
  3. one/
  4. __init__.py
  5. two/
  6. __init__.py
  7. three/
  8. __init__.py

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

5.2.2. 命名空間包

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

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

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

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

5.3. 搜索

為了開始搜索,Python 需要被導入模塊(或者包,對于當前討論來說兩者沒有差別)的完整 限定名稱。 此名稱可以來自 import 語句所帶的各種參數(shù),或者來自傳給 importlib.import_module() 或 __import__() 函數(shù)的形參。

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

5.3.1. 模塊緩存

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

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

sys.modules 是可寫的。刪除鍵可能不會破壞關聯(lián)的模塊(因為其他模塊可能會保留對它的引用),但它會使命名模塊的緩存條目無效,導致 Python 在下次導入時重新搜索命名模塊。鍵也可以賦值為 None ,強制下一次導入模塊導致 ModuleNotFoundError 。

但是要小心,因為如果你還保有對某個模塊對象的引用,同時停用其在 sys.modules 中的緩存條目,然后又再次導入該名稱的模塊,則前后兩個模塊對象將 不是 同一個。 相反地,importlib.reload() 將重用 同一個 模塊對象,并簡單地通過重新運行模塊的代碼來重新初始化模塊內容。

5.3.2. 查找器和加載器

如果指定名稱的模塊在 sys.modules 找不到,則將發(fā)起調用 Python 的導入協(xié)議以查找和加載該模塊。 此協(xié)議由兩個概念性模塊構成,即 查找器 和 加載器。 查找器的任務是確定是否能使用其所知的策略找到該名稱的模塊。 同時實現(xiàn)這兩種接口的對象稱為 導入器 —— 它們在確定能加載所需的模塊時會返回其自身。

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

導入機制是可擴展的,因此可以加入新的查找器以擴展模塊搜索的范圍和作用域。

查找器并不真正加載模塊。 如果它們能找到指定名稱的模塊,會返回一個 模塊規(guī)格說明,這是對模塊導入相關信息的封裝,供后續(xù)導入機制用于在加載模塊時使用。

以下各節(jié)描述了有關查找器和加載器協(xié)議的更多細節(jié),包括你應該如何創(chuàng)建并注冊新的此類對象來擴展導入機制。

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

5.3.3. 導入鉤子

導入機制被設計為可擴展;其中的基本機制是 導入鉤子。 導入鉤子有兩種類型: 元鉤子導入路徑鉤子

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

導入路徑鉤子是作為 sys.path (或 package.__path__) 過程的一部分,在遇到它們所關聯(lián)的路徑項的時候被調用。 導入路徑鉤子的注冊是通過向 sys.path_hooks 添加新的可調用對象,具體如下所述。

5.3.4. 元路徑

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

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

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

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

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

Python 的默認 sys.meta_path 具有三種元路徑查找器,一種知道如何導入內置模塊,一種知道如何導入凍結模塊,還有一種知道如何導入來自 import path 的模塊 (即 path based finder)。

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

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

5.4. 加載

當一個模塊說明被找到時,導入機制將在加載該模塊時使用它(及其所包含的加載器)。 下面是導入的加載部分所發(fā)生過程的簡要說明:

 
 
 
 
  1. module = None
  2. if spec.loader is not None and hasattr(spec.loader, 'create_module'):
  3. # It is assumed 'exec_module' will also be defined on the loader.
  4. module = spec.loader.create_module(spec)
  5. if module is None:
  6. module = ModuleType(spec.name)
  7. # The import-related module attributes get set here:
  8. _init_module_attrs(spec, module)
  9. if spec.loader is None:
  10. # unsupported
  11. raise ImportError
  12. if spec.origin is None and spec.submodule_search_locations is not None:
  13. # namespace package
  14. sys.modules[spec.name] = module
  15. elif not hasattr(spec.loader, 'exec_module'):
  16. module = spec.loader.load_module(spec.name)
  17. # Set __loader__ and __package__ if missing.
  18. else:
  19. sys.modules[spec.name] = module
  20. try:
  21. spec.loader.exec_module(module)
  22. except BaseException:
  23. try:
  24. del sys.modules[spec.name]
  25. except KeyError:
  26. pass
  27. raise
  28. return sys.modules[spec.name]

請注意以下細節(jié):

  • 如果在 sys.modules 中存在指定名稱的模塊對象,導入操作會已經將其返回。

  • 在加載器執(zhí)行模塊代碼之前,該模塊將存在于 sys.modules 中。 這一點很關鍵,因為該模塊代碼可能(直接或間接地)導入其自身;預先將其添加到 sys.modules 可防止在最壞情況下的無限遞歸和最好情況下的多次加載。

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

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

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

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

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

5.4.1. 加載器

模塊加載器提供關鍵的加載功能:模塊執(zhí)行。 導入機制調用 importlib.abc.Loader.exec_module() 方法并傳入一個參數(shù)來執(zhí)行模塊對象。 從 exec_module() 返回的任何值都將被忽略。

加載器必須滿足下列要求:

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

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

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

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

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

在 3.4 版更改: load_module() 方法被 exec_module() 所替代,導入機制會對加載的所有樣板責任作出假定。

為了與現(xiàn)有的加載器兼容,導入機制會使用導入器的 load_module() 方法,如果它存在且導入器也未實現(xiàn) exec_module()。 但是,load_module() 現(xiàn)已棄用,加載器應該轉而實現(xiàn) exec_module()。

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

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

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

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

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

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

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

5.4.2. 子模塊

當使用任意機制 (例如 importlib API, importimport-from 語句或者內置的 __import__()) 加載一個子模塊時,父模塊的命名空間中會添加一個對子模塊對象的綁定。 例如,如果包 spam 有一個子模塊 foo,則在導入 spam.foo 之后,spam 將具有一個 綁定到相應子模塊的 foo 屬性。 假如現(xiàn)在有如下的目錄結構:

 
 
 
 
  1. spam/
  2. __init__.py
  3. foo.py

并且 spam/__init__.py 中有如下幾行內容:

 
 
 
 
  1. from .foo import Foo

那么執(zhí)行如下代碼將把 fooFoo 的名稱綁定添加到 spam 模塊中:

 
 
 
 
  1. >>> import spam
  2. >>> spam.foo
  3. >>> spam.Foo

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

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

導入機制在導入期間會使用有關每個模塊的多種信息,特別是加載之前。 大多數(shù)信息都是所有模塊通用的。 模塊規(guī)格說明的目的是基于每個模塊來封裝這些導入相關信息。

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

模塊的規(guī)格說明會作為模塊對象的 __spec__ 屬性對外公開。 有關模塊規(guī)格的詳細內容請參閱 ModuleSpec。

3.4 新版功能.

5.4.4. 導入相關的模塊屬性

導入機制會在加載期間會根據(jù)模塊的規(guī)格說明填充每個模塊對象的這些屬性,并在加載器執(zhí)行模塊之前完成。

__name__

The __name__ attribute must be set to the fully qualified name of the module. This name is used to uniquely identify the module in the import system.

__loader__

__loader__ 屬性必須被設為導入系統(tǒng)在加載模塊時使用的加載器對象。 這主要是用于內省,但也可用于額外的加載器專用功能,例如獲取關聯(lián)到加載器的數(shù)據(jù)。

__package__

模塊的 __package__ 屬性必須設定。 其取值必須為一個字符串,但可以與 __name__ 取相同的值。 當模塊是包時,其 __package__ 值應該設為其 __name__ 值。 當模塊不是包時,對于最高層級模塊 __package__ 應該設為空字符串,對于子模塊則應該設為其父包名。 更多詳情可參閱 PEP 366。

該屬性取代 __name__ 被用來為主模塊計算顯式相對導入,相關定義見 PEP 366。 預期它與 __spec__.parent 具有相同的值。

在 3.6 版更改: __package__ 預期與 __spec__.parent 具有相同的值。

__spec__

__spec__ 屬性必須設為在導入模塊時要使用的模塊規(guī)格說明。 對 __spec__ 的正確設定將同時作用于 解釋器啟動期間初始化的模塊。 唯一的例外是 __main__,其中的 __spec__ 會 在某些情況下設為 None.

__package__ 未定義時, __spec__.parent 會被用作回退項。

3.4 新版功能.

在 3.6 版更改: 當 __package__ 未定義時,__spec__.parent 會被用作回退項。

__path__

如果模塊為包(不論是正規(guī)包還是命名空間包),則必須設置模塊對象的 __path__ 屬性。 屬性值必須為可迭代對象,但如果 __path__ 沒有進一步的用處則可以為空。 如果 __path__ 不為空,則在迭代時它應該產生字符串。 有關 __path__ 語義的更多細節(jié)將在 下文 中給出。

不是包的模塊不應該具有 __path__ 屬性。

__file__

__cached__

__file__ is optional (if set, value must be a string). It indicates the pathname of the file from which the module was loaded (if loaded from a file), or the pathname of the shared library file for extension modules loaded dynamically from a shared library. It might be missing for certain types of modules, such as C modules that are statically linked into the interpreter, and the import system may opt to leave it unset if it has no semantic meaning (e.g. a module loaded from a database).

If __file__ is set then the __cached__ attribute might also be set, which is the path to any compiled version of the code (e.g. byte-compiled file). The file does not need to exist to set this attribute; the path can simply point to where the compiled file would exist (see PEP 3147).

Note that __cached__ may be set even if __file__ is not set. However, that scenario is quite atypical. Ultimately, the loader is what makes use of the module spec provided by the finder (from which __file__ and __cached__ are derived). So if a loader can load from a cached module but otherwise does not load from a file, that atypical scenario may be appropriate.

5.4.5. module.__path__

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

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

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

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

5.4.6. 模塊的 repr

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

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

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

  • 如果模塊具有 __spec__ 屬性,其中的規(guī)格信息會被用來生成 repr。 被查詢的屬性有 “name”, “l(fā)oader”, “origin” 和 “has_location” 等等。

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

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

  • 對于其他情況,僅在 repr 中使用模塊的 __name__

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

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

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

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

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

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

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

5.5. 基于路徑的查找器

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

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

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

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

基于路徑的查找器還提供了額外的鉤子和協(xié)議以便能擴展和定制可搜索路徑條目的類型。 例如,如果你想要支持網絡 URL 形式的路徑條目,你可以編寫一個實現(xiàn) HTTP 語義在網絡上查找模塊的鉤子。 這個鉤子(可調用對象)應當返回一個支持下述協(xié)議的 path entry finder,以被用來獲取一個專門針對來自網絡的模塊的加載器。

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

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

5.5.1. 路徑條目查找器

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

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

有三個變量由 path based finder, sys.path, sys.path_hooks 和 sys.path_importer_cache 所使用。 包對象的 __path__ 屬性也會被使用。 它們提供了可用于定制導入機制的額外方式。

sys.path contains a list of strings providing search locations for modules and packages. It is initialized from the PYTHONPATH environment variable and various other installation- and implementation-specific defaults. Entries in sys.path can name directories on the file system, zip files, and potentially other “l(fā)ocations” (see the site module) that should be searched for modules, such as URLs, or database queries. Only strings should be present on sys.path; all other data types are ignored.

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

The path based finder iterates over every entry in the search path, and for each of these, looks for an appropriate path entry finder (PathEntryFinder) for the path entry. Because this can be an expensive operation (e.g. there may be stat() call overheads for this search), the path based finder maintains a cache mapping path entries to path entry finders. This cache is maintained in sys.path_importer_cache (despite the name, this cache actually stores finder objects rather than being limited to importer objects). In this way, the expensive search for a particular path entry location’s path entry finder need only be done once. User code is free to remove cache entries from sys.path_importer_cache forcing the path based finder to perform the path entry search again 3.

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

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

如果 sys.path_hooks 中的某個 path entry hook 可調用對象的返回值 一個 path entry finder,則以下協(xié)議會被用來向查找器請求一個模塊的規(guī)格說明,并在加載該模塊時被使用。

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

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

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

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

為了向導入機制提示該規(guī)格說明代表一個命名空間 portion,路徑條目查找器會將 “submodule_search_locations” 設為一個包含該部分的列表。

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

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

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

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

路徑條目查找器的 find_module() 方法已棄用,因為它不允許路徑條目查找器為命名空間包提供部分。 如果 find_loader()find_module() 同時存在于一個路徑條目查找器中,導入系統(tǒng)將總是調用 find_loader() 而不選擇 find_module()。

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

5.6. 替換標準導入系統(tǒng)

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

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

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

5.7. 包相對導入

相對導入使用前綴點號。 一個前綴點號表示相對導入從當前包開始。 兩個或更多前綴點號表示對當前包的上級包的相對導入,第一個點號之后的每個點號代表一級。 例如,給定以下的包布局結構:

 
 
 
 
  1. package/
  2. __init__.py
  3. subpackage1/
  4. __init__.py
  5. moduleX.py
  6. moduleY.py
  7. subpackage2/
  8. __init__.py
  9. moduleZ.py
  10. moduleA.py

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

 
 
 
 
  1. from .moduleY import spam
  2. from .moduleY import spam as ham
  3. from . import moduleY
  4. from ..subpackage1 import moduleY
  5. from ..subpackage2.moduleZ import eggs
  6. from ..moduleA import foo

絕對導入可以使用 import <>from <> import <> 語法,但相對導入只能使用第二種形式;其中的原因在于:

 
 
 
 
  1. import XXX.YYY.ZZZ

應當提供 XXX.YYY.ZZZ 作為可用表達式,但 .moduleY 不是一個有效的表達式。

5.8. 有關 __main__ 的特殊事項

對于 Python 的導入系統(tǒng)來說 __main__ 模塊是一個特殊情況。 正如在 另一節(jié) 中所述,__main__ 模塊是在解釋器啟動時直接初始化的,與 sys 和 builtins 很類似。 但是,與那兩者不同,它并不被嚴格歸類為內置模塊。 這是因為 __main__ 被初始化的方式依賴于發(fā)起調用解釋器所附帶的旗標和其他選項。

5.8.1. __main__.__spec__

根據(jù) __main__ 被初始化的方式,__main__.__spec__ 會被設置相應值或是 None。

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

在 其余的情況 下 __main__.__spec__ 會被設為 None,因為用于填充 __main__ 的代碼不直接與可導入的模塊相對應:

  • 交互型提示

  • -c 選項

  • 從 stdin 運行

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

請注意在最后一種情況中 __main__.__spec__ 總是為 None即使 文件從技術上說可以作為一個模塊被導入。 如果想要讓 __main__ 中的元數(shù)據(jù)生效,請使用 -m 開關。

還要注意即使是在 __main__ 對應于一個可導入模塊且 __main__.__spec__ 被相應地設定時,它們仍會被視為 不同的 模塊。 這是由于以下事實:使用 if __name__ == "__main__": 檢測來保護的代碼塊僅會在模塊被用來填充 __main__ 命名空間時而非普通的導入時被執(zhí)行。

5.9. 參考文獻

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

原始的 sys.meta_path 規(guī)格說明見 PEP 302,后續(xù)的擴展說明見 PEP 420。

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

PEP 366 描述了新增的 __package__ 屬性,用于在模塊中的顯式相對導入。

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

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

PEP 451 在 spec 對象中增加了對每個模塊導入狀態(tài)的封裝。 它還將加載器的大部分樣板責任移交回導入機制中。 這些改變允許棄用導入系統(tǒng)中的一些 API 并為查找器和加載器增加一些新的方法。

備注

1

參見 types.ModuleType。

2

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

3

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


新聞名稱:創(chuàng)新互聯(lián)Python教程:5.導入系統(tǒng)
本文鏈接:http://www.5511xx.com/article/ccsoceo.html