在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 導(dǎo)入系統(tǒng)
復(fù)合語(yǔ)句
數(shù)據(jù)模型
完整的語(yǔ)法規(guī)范
執(zhí)行模型
表達(dá)式
導(dǎo)入系統(tǒng)
詞法分析
簡(jiǎn)單語(yǔ)句
頂層組件
介紹

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

Python 中,一個(gè)模塊的代碼通過(guò)導(dǎo)入程序訪問(wèn)另一個(gè)模塊的代碼。導(dǎo)入語(yǔ)句是調(diào)用導(dǎo)入機(jī)制的最常用的方法,但不是唯一方式。像函數(shù) importlib.import_module() 和內(nèi)建函數(shù) __import__() 也能用來(lái)調(diào)用導(dǎo)入機(jī)制。

導(dǎo)入 語(yǔ)句包含兩個(gè)操作:首先查找指定的模塊,然后將查找結(jié)果綁定到局部作用域內(nèi)的一個(gè)名字上。導(dǎo)入語(yǔ)句中的查找操作被定義為一個(gè)具有適當(dāng)參數(shù)的 __import__() 函數(shù)的調(diào)用。 __import__() 的返回值被用作執(zhí)行導(dǎo)入語(yǔ)句的名稱綁定操作。名稱綁定操作的準(zhǔn)確細(xì)節(jié)請(qǐng)見(jiàn) __import__() 語(yǔ)句。

直接調(diào)用 __import__() 僅執(zhí)行模塊搜索和模塊創(chuàng)建操作(如果查找到的話)。雖然可能發(fā)生某些附帶后果,比如導(dǎo)入父包以及更新不同的緩存(包括 sys.modules),但只有導(dǎo)入語(yǔ)句執(zhí)行名稱綁定操作。

當(dāng)調(diào)用 import() 作為導(dǎo)入語(yǔ)句的一部分,導(dǎo)入系統(tǒng)首先在模塊的全域命名空間中查找指定名稱的函數(shù)。假如沒(méi)有找到,則調(diào)用標(biāo)準(zhǔn)內(nèi)建 __import__() 。調(diào)用導(dǎo)入系統(tǒng)的其他機(jī)制,例如 importlib.import_module(),不執(zhí)行該項(xiàng)核對(duì),并將一直使用標(biāo)準(zhǔn)導(dǎo)入系統(tǒng)。

當(dāng)一個(gè)模塊第一次被導(dǎo)入,Python 搜索該模塊,如果找到,它便創(chuàng)建一個(gè)模塊對(duì)象[1],并初始化。假如不能找到指定的模塊,將引發(fā) ImportError 異常 。當(dāng)調(diào)用導(dǎo)入機(jī)制時(shí),Python 實(shí)現(xiàn)不同的策略去搜索指定的模塊。通過(guò)使用不同的鉤子程序能夠修改和擴(kuò)展這些策略,鉤子程序?qū)⒃谙旅娌糠株U述。

3.3版本中的變化:導(dǎo)入系統(tǒng)已更新到完全能夠?qū)崿F(xiàn) PEP 302的第二階段。不再有隱含的導(dǎo)入機(jī)制——通過(guò) sys.meta_path,整個(gè)導(dǎo)入系統(tǒng)是暴露的。此外,已實(shí)現(xiàn)本地命名空間包支持(見(jiàn)PEP 420)。

導(dǎo)入庫(kù)

importlib 模塊提供豐富的 API 用以與導(dǎo)入系統(tǒng)交互。例如,為調(diào)用導(dǎo)入機(jī)制, importlib.import_module() 提供了一個(gè)推薦的,比內(nèi)建函數(shù) __import__() 更簡(jiǎn)單的API。參閱 importlib 庫(kù)文件了解更多細(xì)節(jié)。

無(wú)論模塊是在 Python、C或是其他語(yǔ)言中實(shí)現(xiàn),Python 只有一個(gè)模塊對(duì)象型態(tài),而且所有模塊都是這個(gè)型態(tài)。為了幫助組織模塊以及提供一個(gè)命名體系,Python 提供了一個(gè)的概念。

你可以將包當(dāng)成一個(gè)文件系統(tǒng)的目錄,將模塊當(dāng)成目錄中的文件,但不能太隨便地做這樣的類比,因?yàn)榘湍K不需要來(lái)自文件系統(tǒng)。為了本文件的目的,我們將使用目錄和文件的這個(gè)方便的類比。類似文件系統(tǒng)目錄,包被分級(jí)組織起來(lái),而且包本身也可以包含子包,常規(guī)模塊也是如此。

重要的是要牢記所有的包都是模塊,但不是所有的模塊都是包?;蛘邠Q一種說(shuō)法,包僅是一種特殊的模塊。具體地說(shuō),任何包含 __path__ 屬性的模塊被認(rèn)為是包。

所有的模塊都有一個(gè)名稱。類似于 Python 標(biāo)準(zhǔn)屬性訪問(wèn)語(yǔ)法,子包與他們父包的名字之間用點(diǎn)隔開(kāi)。因此,你可能有一個(gè)稱為 sys 的模塊和一個(gè)稱為 email 的包,相應(yīng)地你可能有一個(gè)稱為 email.mime 的子包和該子包中一個(gè)稱為 email.mime.text 的模塊。

常規(guī)包

Python 定義兩種型態(tài)包,常規(guī)包命名空間包。常規(guī)包是存在于 Python 3.2 及更早版本中的傳統(tǒng)包。常規(guī)包通常被當(dāng)作含 __init__.py 文件的目錄來(lái)實(shí)現(xiàn)。當(dāng)導(dǎo)入一個(gè)常規(guī)包時(shí),該 __init__.py 文件被隱式執(zhí)行,而且它定義的對(duì)象被綁定到包命名空間中的名稱。 __init__.py 文件能包含其他任何模塊能夠包含的相同的 Python 代碼,而且在導(dǎo)入它時(shí),Python 將給模塊增加一些額外的屬性。

舉個(gè)例子,下面這個(gè)文件系統(tǒng)布局定義了一個(gè)有三個(gè)子包的頂層 parent 包:

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

導(dǎo)入 parent.one 將隱式執(zhí)行 parent/__init__.pyparent/one/__init__.py。 隨后導(dǎo)入 parent.two 或者 parent.three 將分別執(zhí)行 parent/two/__init__.pyparent/three/__init__.py。

命名空間包

命名空間包是不同文件集的復(fù)合,每個(gè)文件集給父包貢獻(xiàn)一個(gè)子包。文件集可以存于文件系統(tǒng)的不同位置。導(dǎo)入過(guò)程中 Python 搜索的壓縮文件,網(wǎng)絡(luò)或者其他地方也可以找到文件集。命名空間包可以也可以不與文件系統(tǒng)的對(duì)象直接對(duì)應(yīng)。他們可以是真實(shí)的模塊但沒(méi)有具體的表述。

命名空間包不使用尋常列表作為他們 __path__ 的屬性。相反,他們使用自定義迭代器型態(tài),如果他們父包的路徑(或者高階包的 sys.path )改變,它將在下次試圖導(dǎo)入時(shí)在該包中自動(dòng)重新搜索包部分。

沒(méi)有帶有命名空間包的 parent/__init__.py 文件。實(shí)際上,導(dǎo)入搜索中可能有多種 parent 目錄存在,而每一個(gè)目錄都被不同的部分提供。因此,parent/one 在物理上可能不是緊挨著 parent/two 邊。在這種情況下,無(wú)論在什么時(shí)候?qū)朐摳赴蛩淖影鼤r(shí),Python 將為高級(jí)別的父包創(chuàng)建一個(gè)命名空間包。

命名空間包的詳細(xì)說(shuō)明也參見(jiàn) PEP 420 。

搜索

搜索之前,Python 需要導(dǎo)入模塊的全限定名(或者包名稱,但為了討論的目的,兩者之間的區(qū)別并不重要)。該名稱可以來(lái)自導(dǎo)入語(yǔ)句的不同參數(shù),或者,來(lái)自 importlib.import_module()__import__() 函數(shù)的參數(shù)。

該名稱將被用在導(dǎo)入搜索的不同階段,并且其可以是一個(gè)子模塊的虛線路徑,比如 foo.bar.baz.。在這種情況中,Python 首先試圖導(dǎo)入 foo,然后是 foo.bar,最后導(dǎo)入 foo.bar.baz。如果中間的任何導(dǎo)入失敗,將引發(fā) ImportError 異常。

模塊緩存

進(jìn)行搜索時(shí),搜索的第一個(gè)地方是 sys.modules。這個(gè)映射作為前期已導(dǎo)入的所有模塊的一個(gè)緩存,包括中間路徑。所以,假如 foo.bar.baz 前期已被導(dǎo)入,那么,sys.modules 將包含進(jìn)入 foofoo.barfoo.bar.baz的入口。每個(gè)鍵都有自己的數(shù)值,都有對(duì)應(yīng)的模塊對(duì)象。

導(dǎo)入過(guò)程中,在 sys.modules 中查找模塊名稱,如果存在,相關(guān)的值是滿足導(dǎo)入的模塊,那么程序完成。然而,如果值為 None,則引發(fā) ImportError 異常。如果未找到模塊名稱,Python 將繼續(xù)搜索模塊。

sys.modules 是可寫的。刪除一個(gè)鍵可能不會(huì)毀壞相關(guān)模塊(因?yàn)槠渌K可以保持引用它),但是它將使指定模塊的緩存入口無(wú)效,導(dǎo)致 Python 在下次導(dǎo)入時(shí)重新搜索指定的模塊。也可以分配鍵值為 None,迫使下一次模塊導(dǎo)入時(shí)引發(fā) ImportError 異常。

注意,假如你保持引用模塊對(duì)象,并使 sys.modules中緩存入口無(wú)效,然后再重新導(dǎo)入指定的模塊,則這兩個(gè)模塊對(duì)象將不相同。對(duì)比之下, imp.reload()將重新使用相同的模塊對(duì)象,并通過(guò)重新運(yùn)行模塊代碼簡(jiǎn)單地重新初始化模塊內(nèi)容。

查找器和加載器

如果指定模塊沒(méi)在 sys.modules 中找到,將調(diào)用 Python 的導(dǎo)入?yún)f(xié)議來(lái)查找和加載模塊。這個(gè)協(xié)議包含兩個(gè)概念性的對(duì)象,查找器加載器。一個(gè)查找器的任務(wù)是決定它是否能夠通過(guò)運(yùn)用其所知的任何策略找到指定模塊。實(shí)現(xiàn)這兩個(gè)接口的對(duì)象稱為 導(dǎo)入器。當(dāng)他們發(fā)現(xiàn)他們能夠加載所需的模塊,他們返回自身。

Python 包括許多默認(rèn)的查找器和導(dǎo)入器。第一個(gè)知道如何定位內(nèi)置模塊,第二個(gè)知道如何定位凍結(jié)模塊。第三個(gè)默認(rèn)查找器搜索模塊的導(dǎo)入路徑。導(dǎo)入路徑是一個(gè)位置的列表,這些位置可以命名文件系統(tǒng)路徑或者壓縮文件。它也可擴(kuò)展到搜索任何可定位的資源,例如被 URLs 識(shí)別的資源。

導(dǎo)入機(jī)制是可擴(kuò)展的,因此新的查找器能夠被添加來(lái)擴(kuò)展模塊搜索的范圍和廣度。

事實(shí)上,查找器不真正加載模塊。如果他們能夠找到指定的模塊,他們返回一個(gè)模塊分支,即模塊導(dǎo)入相關(guān)信息的封裝,在加載模塊時(shí)導(dǎo)入機(jī)制運(yùn)用的信息。

以下部分將更具體講述查找器 和 加載器的協(xié)議,包括如何創(chuàng)建和注冊(cè)新的協(xié)議。
版本3.4中的變化:在 Python 以前的版本中,查找器直接返回加載器,然而現(xiàn)在他們返回含有加載器的模塊分支。加載器在導(dǎo)入中仍被使用,但幾乎沒(méi)有責(zé)任。

導(dǎo)入鉤子程序

導(dǎo)入機(jī)制被設(shè)計(jì)為可擴(kuò)展的;其基礎(chǔ)的運(yùn)行機(jī)制是導(dǎo)入鉤子程序。存在兩種導(dǎo)入鉤子程序的型態(tài):元鉤子程序和導(dǎo)入路徑鉤子程序。

在其他任何導(dǎo)入程序運(yùn)行之前,除了 sys.modules 緩存查找,在導(dǎo)入處理開(kāi)始時(shí)調(diào)用元鉤子程序。這就允許元鉤子程序覆蓋 sys.path 處理程序,凍結(jié)模塊,或甚至內(nèi)建模塊。如下所述,通過(guò)給 sys.meta_path 添加新的查找器對(duì)象注冊(cè)元鉤子程序。

當(dāng)他們的相關(guān)路徑項(xiàng)被沖突時(shí),導(dǎo)入路徑鉤子程序作為 sys.path (或者 package.__path__)處理程序的一部分被調(diào)用。如下所述,通過(guò)給 sys.path_hooks 添加新的調(diào)用來(lái)注冊(cè)導(dǎo)入路徑鉤子程序。

元路徑

sys.modules中沒(méi)有找到指定模塊時(shí),Python 緊接著會(huì)搜索包含一個(gè)元路徑查找器對(duì)象的列表的sys.meta_path。為了查看他們是否知道如何處理指定模塊,這些查找器將被質(zhì)疑。元路徑查找器必須實(shí)現(xiàn) find_spec() 方法,該函數(shù)需要三個(gè)參數(shù):名稱,導(dǎo)入路徑以及(可選)對(duì)象模塊。元路徑查找器能夠使用任何想要使用的策略來(lái)確定是否它能夠處理指定的模塊。

如果元路徑查找器知道如何處理指定模塊,它就返回一個(gè)分支對(duì)象。如果不能處理,就返回 None。假如 sys.meta_path 處理直到其列表最后也沒(méi)有返回一個(gè)分支,則引發(fā) ImportError 異常。僅僅傳播引起的其他任何異常情況將中止導(dǎo)入處理程序。

調(diào)用元路徑查找器的 find_spec() 方法需要兩個(gè)或三個(gè)參數(shù)。第一個(gè)是導(dǎo)入模塊的全限定名,比如 foo.bar.baz。 第二參數(shù)是用來(lái)模塊搜索的路徑入口。對(duì)于高階模塊來(lái)說(shuō),第二個(gè)參數(shù)是 None,但是對(duì)子模塊或者子包來(lái)說(shuō),第二參數(shù)是父包 __path__ 屬性值。如果不能訪問(wèn)準(zhǔn)確的 __path__ 屬性,將引發(fā) ImportError 異常。第三個(gè)參數(shù)是一個(gè)已存在的對(duì)象,其隨后將是加載的目標(biāo)。導(dǎo)入系統(tǒng)僅在重新加載時(shí)通過(guò)一個(gè)目標(biāo)模塊。

一個(gè)單一的導(dǎo)入要求可以多次遍歷元路徑。舉例說(shuō)明,假設(shè)還沒(méi)有緩存任何所涉的模塊,在每個(gè)元路徑查找器(mpf)上調(diào)用 mpf.find_spec("foo", None, None),導(dǎo)入 foo.bar.baz 將首先執(zhí)行一個(gè)高階導(dǎo)入。導(dǎo)入 foo 后,調(diào)用 mpf.find_spec("foo.bar", foo.__path__, None),通過(guò)再一次遍歷元路徑,foo.bar 將被導(dǎo)入。一旦完成導(dǎo)入 foo.bar,最后的遍歷將調(diào)用 mpf.find_spec("foo.bar.baz", foo.bar.__path__, None)。

一些元路徑查找器僅支持高階導(dǎo)入。當(dāng)除 None 以外的的任何東西作為第二參數(shù)通過(guò)時(shí),這些導(dǎo)入器將一直返回 None。

Python 默認(rèn)的 sys.meta_path 有三個(gè)元路徑查找器,一個(gè)知道如何導(dǎo)入內(nèi)建模塊,一個(gè)知道如何導(dǎo)入凍結(jié)模塊,另一個(gè)知道如何從一個(gè)導(dǎo)入路徑(即路徑查找器)中導(dǎo)入模塊。

版本3.4中的變化:元路徑查找器的 find_spec() 方法取代了現(xiàn)已不被推薦使用的 find_module()。盡管它將繼續(xù)無(wú)變化地運(yùn)行,但僅當(dāng)查找器不執(zhí)行 find_spec() 時(shí),導(dǎo)入機(jī)制才將試圖實(shí)現(xiàn)它。

加載過(guò)程

當(dāng)找到一個(gè)模塊分支時(shí),在加載模塊時(shí)導(dǎo)入機(jī)將使用它(及它包含的加載器)。下面是導(dǎo)入加載部分時(shí)所發(fā)生的近似值:

    module = None  
    if spec.loader is not None and hasattr(spec.loader, 'create_module'):
        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:
        if spec.submodule_search_locations is not None:
            # namespace package
            sys.modules[spec.name] = module
        else:
            # unsupported
            raise ImportError
    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]  

注意以下細(xì)節(jié):

  • 如果在 sys.modules 中存在一個(gè)指定名稱的模塊對(duì)象,導(dǎo)入將已返回它。
  • 在加載器執(zhí)行模塊代碼前,模塊存在在 sys.modules 中。這是重要的,因?yàn)槟K代碼可以自我導(dǎo)入(直接或間接地);預(yù)先把它添加到 sys.modules 中能阻止最壞情況下的無(wú)限遞推和最好情況下的多重加載。
  • 假如加載失敗了,這個(gè)失敗的模塊(僅是這個(gè)失敗的模塊)將從 sys.modules 里移除。任何已在 sys.modules 緩存中的模塊,以及任何附帶成功加載的模塊,都一定留存在緩存之中。這與重新加載時(shí)失敗模塊也能存留在 sys.modules 中的情況相反。
  • 后面部分總結(jié)的,創(chuàng)建模塊后但在執(zhí)行前,導(dǎo)入機(jī)制設(shè)置導(dǎo)入相關(guān)模塊的屬性(以上偽代碼案例中的 “_init_module_attrs”)。
  • 模塊運(yùn)行是模塊命名空間密集加載的關(guān)鍵時(shí)刻。運(yùn)行完全交給了能夠決定什么密集以及怎樣密集的加載器。
  • 加載期間創(chuàng)建的和通過(guò) exec_module() 的模塊可能不是導(dǎo)入結(jié)束時(shí)返回的那個(gè)[2]。
    3.4版本中的變化:導(dǎo)入系統(tǒng)接替了加載器的公式化任務(wù)。這些以前由 importlib.abc.Loader.load_module() 方法執(zhí)行。

加載器

模塊加載器提供加載的判定函數(shù):模塊執(zhí)行。導(dǎo)入機(jī)制用要執(zhí)行的模塊對(duì)象的單一參數(shù)調(diào)用 importlib.abc.Loader.exec_module() 方法。任何從 exec_module()返回的值將被忽略。
加載器必須滿足下列要求:

  • 假設(shè)模塊是一個(gè) Python 模塊(與內(nèi)建模塊或動(dòng)態(tài)加載擴(kuò)展相反),加載器應(yīng)該在模塊全域命名空間(module.__dict__)中執(zhí)行該模塊代碼。
  • 假如加載器不能執(zhí)行模塊,它應(yīng)該引發(fā) ImportError 異常,即使將傳播 exec_module() 引發(fā)的任何其他異常情況。

在許多例子中,查找器和加載器可以是相同的對(duì)象;在這種情況中,find_spec() 方法將僅返回一個(gè)加載器設(shè)置為 self 的分支。

模塊加載器可以選擇在加載時(shí)通過(guò)實(shí)現(xiàn)一個(gè) create_module() 方法來(lái)創(chuàng)建模塊對(duì)象。它需要一個(gè)參數(shù),即模塊分支,并在加載中返回新的模塊對(duì)象用以使用。create_module() 不需要在模塊對(duì)象上設(shè)置任何屬性。假如加載器不定義 create_module(),導(dǎo)入機(jī)制本身將創(chuàng)建新的模塊。

3.4版本中的更新:加載器的 create_module() 方法。

3.4版本中的變化:load_module() 方法被 exec_module() 取代,并且導(dǎo)入機(jī)制承擔(dān)了所有加載的公式化任務(wù)。

為了與存在的加載器相兼容,導(dǎo)入機(jī)制將使用加載器的 load_module 方法(如果存在,并且加載器也不實(shí)現(xiàn) exec_module() 的話)。然而, load_module() 已不被推崇,加載器應(yīng)替代實(shí)現(xiàn) exec_module()。

除了運(yùn)行模塊之外,load_module()方法必須實(shí)現(xiàn)以上所述的所有的公式化加載功能。經(jīng)過(guò)一些補(bǔ)充說(shuō)明,所有相同的約束條件都可應(yīng)用。

  • 假設(shè)在 sys.modules 中存在一個(gè)命名的模塊對(duì)象,加載器必須使用這個(gè)存在的模塊。(否則,importlib.reload()不能正確運(yùn)行)假如在 sys.modules中不存在指定模塊,加載器必須創(chuàng)建一個(gè)新的模塊對(duì)象并把它添加到 sys.modules 中。
  • 在加載器運(yùn)行模塊代碼前,sys.modules 中必須存在模塊,以阻止無(wú)限遞推或多重加載。
  • 假如加載失敗,加載器必須移除其嵌入 sys.modules 中的所有模塊。但僅當(dāng)加載器本身已顯示加載過(guò)它時(shí),它必須只移除失敗模塊。

模塊分支

導(dǎo)入機(jī)制在導(dǎo)入中,尤其在加載前,使用有關(guān)每個(gè)模塊的多種信息。大部分信息對(duì)所有模塊都是共用的。模塊分支的目的是在每個(gè)模塊基礎(chǔ)上封裝導(dǎo)入相關(guān)信息。

在導(dǎo)入時(shí)使用一個(gè)分支允許語(yǔ)句在導(dǎo)入系統(tǒng)組件間傳輸,例如,在創(chuàng)建模塊分支的查找器和運(yùn)行它的加載器之間傳輸。最重要的是,它允許導(dǎo)入機(jī)制執(zhí)行加載的公式化操作,而如果沒(méi)有模塊分支,加載器承擔(dān)那項(xiàng)任務(wù)。

有關(guān)模塊分支能擁有的相關(guān)信息,更詳細(xì)地參見(jiàn) ModuleSpec。

3.4版本的更新.

導(dǎo)入關(guān)聯(lián)模塊屬性

在加載器運(yùn)行模塊前,根據(jù)模塊分支,導(dǎo)入機(jī)制在加載中在每個(gè)模塊上填入下列屬性。

__name__

__name__屬性必須設(shè)置為模塊的全限定名。該名稱用來(lái)唯一識(shí)別導(dǎo)入系統(tǒng)的模塊。

__loader__

__loader__屬性必須設(shè)置為加載模塊時(shí)導(dǎo)入機(jī)制使用的加載器對(duì)象。這主要是為了自我檢查,但也可以用作加載器專用的附加功能,例如獲得一個(gè)加載器相關(guān)的數(shù)據(jù)。

__package__
模塊的__package__屬性必須設(shè)置。它的值必須是一個(gè)字符串,但可以與其__name__是相同的值。當(dāng)模塊是包時(shí),其__package__值應(yīng)該設(shè)定為它的_name__。當(dāng)模塊不是包時(shí),對(duì)于高階模塊__package__應(yīng)該設(shè)置為空字符串,對(duì)于子模塊,應(yīng)設(shè)置為父包名稱。具體詳見(jiàn) PEP 366

該屬性可以替代 __name__ 用作計(jì)算主要模塊的顯示相關(guān)導(dǎo)入,如 PEP 366 所定義的。

__spec__ __spec__屬性必須設(shè)置為導(dǎo)入模塊時(shí)所使用的模塊分支。這主要用作自查以及主要用在加載中。準(zhǔn)確設(shè)置 __spec__ 同樣應(yīng)用于解釋器啟動(dòng)中初始化的模塊。有一個(gè)例外是 __main__,在一些例子中在 __spec__ 設(shè)置為 None。

3.4版本的更新:

__path__

假如模塊是個(gè)包(不管常規(guī)包還是命名空間包),該模塊對(duì)象的 __path__ 屬性必須設(shè)置。值必須是迭代的,但如果 __path__ 沒(méi)有更深的意義,值也可以是空的。如果 __path__ 不是空的,迭代結(jié)束時(shí)它必須產(chǎn)生字符串。更多有關(guān) __path__ 語(yǔ)義學(xué)的細(xì)節(jié)將在下面給出。 無(wú)包模塊不應(yīng)有 __path__ 屬性。

__file__
__cached__

__file__ 是可選的。如果設(shè)置,該屬性的值必須是一個(gè)字符串。如果沒(méi)有語(yǔ)義學(xué)意義(例如從數(shù)據(jù)庫(kù)中加載的一個(gè)模塊),導(dǎo)入系統(tǒng)可以選擇不設(shè) __file__。

如果設(shè)置了 __file__,也可以適當(dāng)設(shè)置 __cached__ 屬性,該屬性是任何代碼編譯版本的路徑(例如字節(jié)編譯文件)。設(shè)置該屬性不需要文件存在;路徑能夠簡(jiǎn)單地指向編譯文件存在的位置。(見(jiàn) PEP 3147)。

沒(méi)有設(shè)置 __file__ 時(shí),也可以設(shè)置 __cached__ 。然而,那種情況相當(dāng)非典型。最終,加載器是利用 __file__ 和/或 __cached__ 的使用者。所以假如一個(gè)加載器能夠從一個(gè)緩存模塊中加載而不能從一個(gè)文件中加載的話,適用這個(gè)非典型的情況可能是合適的。

module.__path__

根據(jù)定義,如果一個(gè)模塊有一個(gè) __path__ 屬性,無(wú)論它的值是什么,它就是一個(gè)包。

一個(gè)包的 __path__ 屬性在導(dǎo)入其子包時(shí)使用。在導(dǎo)入機(jī)制中,它與 sys.path 的功能很相同,即在導(dǎo)入時(shí)提供一個(gè)搜索模塊的位置列表。然而,通常 __path__sys.path 限制更多。

__path__ 必須是字符串的一個(gè)迭代,但它可以為空。sys.path 中使用的相同的規(guī)則也適用包的 __path__,并且當(dāng)遍歷包路徑時(shí),將求助sys.path_hooks 下面將講到)。

一個(gè)包的 __init__.py 文件可以設(shè)置或改變?cè)摪?__path__ 屬性,并且這也是 PEP 420 之前實(shí)現(xiàn)命名空間包的典型方式。采用 PEP 420之后,命名空間包不再需要提供只包含 __path__ 操作代碼的 __init__.py 的文件;導(dǎo)入機(jī)制自動(dòng)準(zhǔn)確設(shè)置命名空間包的 __path__。

模塊 reprs

通過(guò)默認(rèn),所有模塊都有一個(gè)可用的表示,然而,根據(jù)上述所設(shè)的各屬性,在模塊分支中,你可以更明確地控制各模塊對(duì)象的表示。

假如模塊存在一個(gè)分支 (__spec__),導(dǎo)入機(jī)制將試圖從它那生成一個(gè)表示。如果失敗或不存在分支,導(dǎo)入系統(tǒng)將使用模塊上任何可用信息制作一個(gè)默認(rèn)的表示。它將嘗試使用 module.__name__,module.__file__module.__loader__ 輸入到該表示中,并默認(rèn)缺失的任何信息。

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

  • 假如模塊有一個(gè) __spec__ 屬性,該分支上的信息被用作產(chǎn)生表示。“name”, “l(fā)oader”, “origin”, 和 “has_location”這些屬性將被求助。

  • 假如模塊有一個(gè) __file__ 屬性,這用作模塊表示的一部分。

  • 假如模塊沒(méi)有 __file__ 但確有一個(gè)不是None的 __loader__,那么該 加載器的表示用作模塊表示的一部分。

  • 或者,僅在表示中使用模塊的 __name__

3.4版本的變化:使用loader.module_repr()已不被推崇,而且模塊分支現(xiàn)在被導(dǎo)入機(jī)制用作產(chǎn)生模塊表示。

為了與 Python 3.3 向后兼容,在嘗試以上所述任一方法前,假如定義過(guò),將通過(guò)調(diào)用加載器的 module_repr() 方法產(chǎn)生模塊表示。然而,不推薦使用該方法。

基于路徑的查找器

如前所提及的,Python 本身帶有幾個(gè)默認(rèn)的元路徑查找器。其中之一,稱作基于路徑的查找器(路徑查找器),搜索含有一個(gè)路徑入口列表的一個(gè)導(dǎo)入路徑。每個(gè)路徑入口都指定一個(gè)搜索模塊的位置。

基于路徑的查找器本身不知道如何導(dǎo)入東西。相反,它遍歷單一路徑入口,將每個(gè)入口與知道如何處理這種特定路徑的路徑入口查找器結(jié)合起來(lái)。

路徑入口查找器的默認(rèn)設(shè)置實(shí)現(xiàn)所有在文件系統(tǒng)尋找模塊的語(yǔ)義,處理類似 Python 資源代碼 (.py 文件), Python 字節(jié)代碼 (.pyc.pyo 文件) 以及 共享庫(kù) (即 .so文件)的特殊文件型態(tài)。當(dāng)標(biāo)準(zhǔn)庫(kù)中 zipimport 模塊支持的話,默認(rèn)路徑入口查找器也處理從壓縮文件中加載這些文件型態(tài)(除了共享庫(kù)以外)的一切。

路徑入口不需要受制于文件系統(tǒng)位置。他們可以引用 URLs,數(shù)據(jù)庫(kù)查詢,或者其他能夠用一個(gè)字符串指定的位置。

路徑基礎(chǔ)查找器提供附加的鉤子程序和協(xié)議,因此你能夠擴(kuò)展和自定義可搜索的路徑入口型態(tài)。例如,假如你想要支持路徑入口作為網(wǎng)絡(luò) URL,你可以寫一個(gè)實(shí)現(xiàn) HTTP 語(yǔ)義并能在網(wǎng)絡(luò)上查找模塊的鉤子程序。這個(gè)鉤子程序(一個(gè)調(diào)用)將返回一個(gè)支持下述協(xié)議的路徑入口查找器,其隨后用作從網(wǎng)絡(luò)獲得模塊的一個(gè)加載器。

注意事項(xiàng):這節(jié)和上一節(jié)都使用術(shù)語(yǔ)查找器,區(qū)別它們通過(guò)使用術(shù)語(yǔ)元路徑查找器路徑入口查找器。這兩種查找器的型態(tài)非常相似,都支持相似的協(xié)議,以及在導(dǎo)入程序中起相似的作用,但重要的是要牢記他們稍微有所不同。尤其,元路徑查找器在導(dǎo)入程序開(kāi)始時(shí)操作,切斷 sys.meta_path 遍歷。

相比之下,在某種意義上,路徑入口查找器是實(shí)現(xiàn)基于路徑查找器的一個(gè)詳述,并且事實(shí)上,如果將路徑基礎(chǔ)查找器從 sys.meta_path 中移除,沒(méi)有任何路徑入口查找器語(yǔ)義將被調(diào)用。

路徑入口查找器

基于路徑的查找器 負(fù)責(zé)查找和加載 Python 模塊和包,他們可以利用一個(gè)字符串 路徑入口 指明地址。大多數(shù)路徑入口在文件系統(tǒng)中指定地址,但他們不必受限于此。

作為一個(gè)元路徑查找器, 基于路徑的查找器 實(shí)現(xiàn)先前所述的 find_spec() 協(xié)議,然而它曝光可以用來(lái)自定義如何從導(dǎo)入路徑查找和加載模塊的附加鉤子程序。

基于路徑的查找器使用三個(gè)變量,sys.pathsys.path_hookssys.path_importer_cache。 同時(shí)也使用包對(duì)象上的 __path__ 屬性。這些提供了能夠自定義導(dǎo)入機(jī)制的其他方法。

sys.path 含有一個(gè)提供模塊和包的搜索位置的字符串列表。它從 PYTHONPATH 環(huán)境變量和其他不同安裝專用和實(shí)現(xiàn)專用的默認(rèn)中初始化。sys.path的入口能夠指定文件系統(tǒng),壓縮文件和其他潛在“位置”(見(jiàn)site模塊)的目錄,這些“位置”,比如 URLs, 或者數(shù)據(jù)庫(kù)查詢,應(yīng)被用作搜索模塊。sys.path 應(yīng)僅顯示字符串和字節(jié);忽略其他所有數(shù)據(jù)型態(tài)。字節(jié)入口的編碼由個(gè)別路徑入口查找器決定。

基于路徑的查找器 是一個(gè)元路徑查找器,所以導(dǎo)入機(jī)制通過(guò)調(diào)用先前所述的路徑入口查找器的 find_spec() 方法開(kāi)啟導(dǎo)入路徑 搜索。當(dāng)給出 find_spec()path 參數(shù),它將是一個(gè)要遍歷的字符串路徑的列表,通常是包內(nèi)導(dǎo)入中一個(gè)包的 __path__ 屬性。假如 path 變量是 None,這表明使用一個(gè)高階導(dǎo)入和 sys.path。

基于路徑的查找器在搜索路徑中迭代每個(gè)入口,并且每個(gè)為路徑入口尋找一個(gè)適當(dāng)?shù)?a rel="nofollow" >路徑入口查找器。因?yàn)檫@可能是一個(gè)耗時(shí)的操作程序(例如,本次搜索有可能存在的 stat() 調(diào)用的耗時(shí)),基于路徑的查找器維持一個(gè)路徑入口查找器的緩存映射路徑入口。這個(gè)緩存維持在 sys.path_importer_cache(盡管名稱如此,這個(gè)緩存實(shí)際上儲(chǔ)存查找器對(duì)象而不限制于導(dǎo)入器對(duì)象)。因此,這個(gè)耗時(shí)搜索,即查找個(gè)別路徑入口位置的路徑入口查找器,僅需要完成一次即可。用戶代碼可以自由將緩存入口從迫使path based finder再一次執(zhí)行路徑入口搜索的sys.path_importer_cache中移除[3]。

假如路徑入口不在緩存中顯示,基于路徑的查找器將迭代 sys.path_hooks 中每個(gè)調(diào)用。使用單參數(shù)調(diào)用該列表中的每個(gè)路徑入口鉤子程序,即要搜索的路徑入口。這個(gè)調(diào)用或者可能返回一個(gè)能夠處理路徑入口的路徑入口查找器 ,或者可能引發(fā) ImportError異常。,基于路徑的查找器使用 ImportError 異常來(lái)暗示鉤子程序無(wú)法找到指定路徑入口路徑入口查找器。忽略異常情況, 導(dǎo)入路徑迭代繼續(xù)。鉤子程序應(yīng)該預(yù)料到或者會(huì)出現(xiàn)一個(gè)字符串或者會(huì)出現(xiàn)一個(gè)字節(jié)對(duì)象;字節(jié)對(duì)象的編程取決于鉤子程序(例如,它可能是一個(gè)文件系統(tǒng)編程,UTF-8 或其他),而且假如鉤子程序不能解碼參數(shù),它會(huì)引發(fā) ImportError 異常。

假如 sys.path_hooks 迭代結(jié)束后沒(méi)有路徑入口查找器返回,那么基于路徑的查找器的 find_spec() 方法將在 sys.path_importer_cache 儲(chǔ)存 None(表示沒(méi)有該路徑入口的查找器)并返回 None,表示該元路徑查找器不能找到模塊。

假如 [sys.path_hooks]() 的一個(gè)路徑入口鉤子程序調(diào)用返回一個(gè)路徑入口查找器,則使用下面的協(xié)議用作向查找器請(qǐng)求一個(gè)加載模塊時(shí)使用的模塊分支。

路徑入口查找器協(xié)議

為了支持導(dǎo)入模塊和初始化包以及擴(kuò)充命名空間,路徑入口查找器必須實(shí)現(xiàn) find_spec() 方法。

find_spec() 方法需要兩個(gè)參數(shù),導(dǎo)入模塊的全限定名,以及(可選)目標(biāo)模塊。find_spec() 返回完全填充的模塊分支。該分支將一直擁有“加載器”設(shè)置(但有一個(gè)例外)。

導(dǎo)入機(jī)制中,分支代表命名空間的一部分。路徑入口查找器設(shè)置分支的 “l(fā)oader” 為 None 并且設(shè)置 “submodule_search_locations” 為一個(gè)包含該部分的列表。

3.4版本的變化: find_spec() 替代了 find_loader()find_module(),這兩個(gè)現(xiàn)在都不被推薦使用,但在沒(méi)有定義 find_spec() 時(shí)扔將使用。

早期的路徑入口查找器可能實(shí)現(xiàn)這兩個(gè)不被推崇的函數(shù)之一,而代替 find_spec()。為了向后兼容,這些方法仍被贊譽(yù)。但是如果在路徑入口查找器上實(shí)現(xiàn) find_spec(),那么遺留的方法將被忽略。

find_loader()需要一個(gè)參數(shù),即導(dǎo)入模塊的全限定名。find_loader() 返回一個(gè)二元運(yùn)算符,其中第一項(xiàng)是加載器,第二項(xiàng)是一個(gè)命名空間的部分。當(dāng)?shù)谝豁?xiàng)(即加載器)是 None 時(shí),這表明即使路徑入口查找器沒(méi)有指定模塊的加載器,但它知道路徑入口擴(kuò)展指定模塊的命名空間[部分]。這種情況幾乎一直存在于請(qǐng)求Python導(dǎo)入沒(méi)有物理顯示的命名空間包的文件系統(tǒng)時(shí)。路徑入口查找器返回加載器的值為None時(shí),二元運(yùn)算符的返回值的第二項(xiàng)必須是一個(gè)序列,盡管它可以是空的。

假如 find_loader()返回一個(gè)不是 None 的加載器的值,該部分忽略,并且從基于路徑查找器返回加載器,并通過(guò)路徑入口結(jié)束搜索。

為了向后兼容其他導(dǎo)入?yún)f(xié)議的實(shí)現(xiàn)程序,許多路徑入口查找器同時(shí)支持與元路徑查找器支持的相同的、傳統(tǒng)的 find_module() 方法。然而路徑入口查找器的 find_module() 方法從來(lái)沒(méi)有用一個(gè) path 參數(shù)調(diào)用(他們預(yù)計(jì)會(huì)記錄路徑鉤子程序的初始調(diào)用的準(zhǔn)確路徑信息)。

由于不允許路徑入口查找器擴(kuò)展命名空間包,因此路徑入口查找器 上的 find_module() 方法不被推薦使用。如果 find_loader()find_module() 同時(shí)存在于一個(gè)路徑入口查找器,導(dǎo)入系統(tǒng)將一直優(yōu)先調(diào)用 find_loader() 而不是 find_module()。

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

最可靠的完全替代導(dǎo)入系統(tǒng)的機(jī)制是刪除 sys.meta_path 的默認(rèn)內(nèi)容,用一個(gè)自定義的元路徑鉤子程序完全代替他們。

如僅改變導(dǎo)入語(yǔ)句的行為而不影響訪問(wèn)導(dǎo)入系統(tǒng)的其他 APIs 是可以接受的話,那么代替內(nèi)建 __import__() 函數(shù)可能就足夠了。也可以在模塊級(jí)上采用這個(gè)技術(shù)僅在該模塊中改變導(dǎo)入語(yǔ)句行為。

在元路徑早期,鉤子程序選擇性地阻止一些模塊的導(dǎo)入(而不是完全將標(biāo)準(zhǔn)導(dǎo)入系統(tǒng)無(wú)效),直接在 find_spec() 引發(fā) ImportError 異常而不是返回 None。后者表明元路徑搜索應(yīng)該繼續(xù),而引發(fā)異常情況將立即終止程序。

__main__

__main__ 模塊相對(duì) Python 導(dǎo)入系統(tǒng)是一個(gè)特殊案例。像 elsewhere 中明確的,更像 sysbuiltins, __main__ 模塊在解釋器啟動(dòng)中直接初始化。然而,不像這兩個(gè),它不是嚴(yán)格意義上的內(nèi)建模塊。這是因?yàn)?__main__ 初始化的方法取決于標(biāo)志和用來(lái)調(diào)用解釋器的其他選項(xiàng)。

__main__.__spec__

根據(jù)初始化 __main__ 的方式,__main__.__spec__ 獲得正確地設(shè)置或者設(shè)置為 None。

Python 以上一篇:詞法分析下一篇:完整的語(yǔ)法規(guī)范