<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          初窺 Python 的 import 機制

          共 6626字,需瀏覽 14分鐘

           ·

          2021-02-09 09:24

          本文適合有 Python 基礎的小伙伴進階學習

          作者:pwwang

          一、前言

          本文基于開源項目:

          https://github.com/pwwang/python-import-system

          補充擴展講解,希望能夠讓讀者一文搞懂 Python 的 import 機制。

          1.1 什么是 import 機制?

          通常來講,在一段 Python 代碼中去執(zhí)行引用另一個模塊中的代碼,就需要使用 Python 的 import 機制。import 語句是觸發(fā) import 機制最常用的手段,但并不是唯一手段。

          importlib.import_module__import__ 函數(shù)也可以用來引入其他模塊的代碼。

          1.2 import 是如何執(zhí)行的?

          import 語句會執(zhí)行兩步操作:

          1. 搜索需要引入的模塊
          2. 將模塊的名字做為變量綁定到局部變量中

          搜索步驟實際上是通過 __import__ 函數(shù)完成的,而其返回值則會作為變量被綁定到局部變量中。下面我們會詳細聊到 __import__ 函數(shù)是如果運作的。

          二、import 機制概覽

          下圖是 import 機制的概覽圖。不難看出,當 import 機制被觸發(fā)時,Python 首先會去 sys.modules 中查找該模塊是否已經被引入過,如果該模塊已經被引入了,就直接調用它,否則再進行下一步。這里 sys.modules 可以看做是一個緩存容器。值得注意的是,如果 sys.modules 中對應的值是 None 那么就會拋出一個 ModuleNotFoundError 異常。下面是一個簡單的實驗:

          In?[1]:?import?sys

          In?[2]:?sys.modules['os']?=?None

          In?[3]:?import?os
          ---------------------------------------------------------------------------
          ModuleNotFoundError???????????????????????Traceback?(most?recent?call?last)
          -3-543d7f3a58ae>?in?
          ---->?1?import?os

          ModuleNotFoundError:?import?of?os?halted;?None?in?sys.modules

          如果在 sys.modules 找到了對應的 module,并且這個 import 是由 import 語句觸發(fā)的,那么下一步將對把對應的變量綁定到局部變量中。

          如果沒有發(fā)現(xiàn)任何緩存,那么系統(tǒng)將進行一個全新的 import 過程。在這個過程中 Python 將遍歷 sys.meta_path 來尋找是否有符合條件的元路徑查找器(meta path finder)。sys.meta_path 是一個存放元路徑查找器的列表。它有三個默認的查找器:

          • 內置模塊查找器
          • 凍結模塊(frozen module)查找器
          • 基于路徑的模塊查找器。
          In?[1]:?import?sys

          In?[2]:?sys.meta_path
          Out[2]:?
          [_frozen_importlib.BuiltinImporter,
          ?_frozen_importlib.FrozenImporter,
          ?_frozen_importlib_external.PathFinder]

          查找器的 find_spec 方法決定了該查找器是否能處理要引入的模塊并返回一個 ModeuleSpec 對象,這個對象包含了用來加載這個模塊的相關信息。如果沒有合適的 ModuleSpec 對象返回,那么系統(tǒng)將查看 sys.meta_path 的下一個元路徑查找器。如果遍歷 sys.meta_path 都沒有找到合適的元路徑查找器,將拋出 ModuleNotFoundError。引入一個不存在的模塊就會發(fā)生這種情況,因為 sys.meta_path 中所有的查找器都無法處理這種情況:

          In?[1]:?import?nosuchmodule
          ---------------------------------------------------------------------------
          ModuleNotFoundError???????????????????????Traceback?(most?recent?call?last)
          -1-40c387f4d718>?in?
          ---->?1?import?nosuchmodule

          ModuleNotFoundError:?No?module?named?'nosuchmodule'

          但是,如果這個手動添加一個可以處理這個模塊的查找器,那么它也是可以被引入的:

          In?[1]:?import?sys
          ???...:?
          ???...:?from?importlib.abc?import?MetaPathFinder
          ???...:?from?importlib.machinery?import?ModuleSpec
          ???...:?
          ???...:?class?NoSuchModuleFinder(MetaPathFinder):
          ???...:?????def?find_spec(self,?fullname,?path,?target=None):
          ???...:?????????return?ModuleSpec('nosuchmodule',?None)
          ???...:?
          ???...:?#?don't?do?this?in?your?script
          ???...:?sys.meta_path?=?[NoSuchModuleFinder()]
          ???...:?
          ???...:?import?nosuchmodule
          ---------------------------------------------------------------------------
          ImportError???????????????????????????????Traceback?(most?recent?call?last)
          -6-b7cbf7e60adc>?in?
          ?????11?sys.meta_path?=?[NoSuchModuleFinder()]
          ?????12?
          --->?13?import?nosuchmodule

          ImportError:?missing?loader

          可以看到,當我們告訴系統(tǒng)如何去 find_spec 的時候,是不會拋出 ModuleNotFound 異常的。但是要成功加載一個模塊,還需要加載器 loader

          加載器是 ModuleSpec 對象的一個屬性,它決定了如何加載和執(zhí)行一個模塊。如果說 ModuleSpec 對象是“師父領進門”的話,那么加載器就是“修行在個人”了。在加載器中,你完全可以決定如何來加載以及執(zhí)行一個模塊。這里的決定,不僅僅是加載和執(zhí)行模塊本身,你甚至可以修改一個模塊:

          In?[1]:?import?sys
          ???...:?from?types?import?ModuleType
          ???...:?from?importlib.machinery?import?ModuleSpec
          ???...:?from?importlib.abc?import?MetaPathFinder,?Loader
          ???...:?
          ???...:?class?Module(ModuleType):
          ???...:?????def?__init__(self,?name):
          ???...:?????????self.x?=?1
          ???...:?????????self.name?=?name
          ???...:?
          ???...:?class?ExampleLoader(Loader):
          ???...:?????def?create_module(self,?spec):
          ???...:?????????return?Module(spec.name)
          ???...:?
          ???...:?????def?exec_module(self,?module):
          ???...:?????????module.y?=?2
          ???...:?
          ???...:?class?ExampleFinder(MetaPathFinder):
          ???...:?????def?find_spec(self,?fullname,?path,?target=None):
          ???...:?????????return?ModuleSpec('module',?ExampleLoader())
          ???...:?
          ???...:?sys.meta_path?=?[ExampleFinder()]

          In?[2]:?import?module

          In?[3]:?module
          Out[3]:?'module'?(<__main__.ExampleLoader?object?at?0x7f7f0d07f890>)>

          In?[4]:?module.x
          Out[4]:?1

          In?[5]:?module.y
          Out[5]:?2

          從上面的例子可以看到,一個加載器通常有兩個重要的方法 create_moduleexec_module 需要實現(xiàn)。如果實現(xiàn)了 exec_module 方法,那么 create_module 則是必須的。如果這個 import 機制是由 import 語句發(fā)起的,那么 create_module 方法返回的模塊對象對應的變量將會被綁定到當前的局部變量中。如果一個模塊因此成功被加載了,那么它將被緩存到 sys.modules。如果這個模塊再次被加載,那么 sys.modules 的緩存將會被直接引用。

          三、import 勾子(import hooks)

          為了簡化,我們在上述的流程圖中,并沒有提到 import 機制的勾子。實際上你可以添加一個勾子來改變 sys.meta_path 或者 sys.path,從而來改變 import 機制的行為。上面的例子中,我們直接修改了 sys.meta_path。實際上,你也可以通過勾子來實現(xiàn):

          In?[1]:?import?sys
          ???...:?from?types?import?ModuleType
          ???...:?from?importlib.machinery?import?ModuleSpec
          ???...:?from?importlib.abc?import?MetaPathFinder,?Loader
          ???...:?
          ???...:?class?Module(ModuleType):
          ???...:?????def?__init__(self,?name):
          ???...:?????????self.x?=?1
          ???...:?????????self.name?=?name
          ???...:?
          ???...:?class?ExampleLoader(Loader):
          ???...:?????def?create_module(self,?spec):
          ???...:?????????return?Module(spec.name)
          ???...:?
          ???...:?????def?exec_module(self,?module):
          ???...:?????????module.y?=?2
          ???...:?
          ???...:?class?ExampleFinder(MetaPathFinder):
          ???...:?????def?find_spec(self,?fullname,?path,?target=None):
          ???...:?????????return?ModuleSpec('module',?ExampleLoader())
          ???...:?
          ???...:?def?example_hook(path):
          ???...:?????#?some?conditions?here
          ???...:?????return?ExampleFinder()
          ???...:?
          ???...:?sys.path_hooks?=?[example_hook]
          ???...:?#?force?to?use?the?hook
          ???...:?sys.path_importer_cache.clear()
          ???...:?
          ???...:?import?module
          ???...:?module
          Out[1]:?'module'?(<__main__.ExampleLoader?object?at?0x7fdb08f74b90>)>

          四、元路徑查找器(meta path finder)

          元路徑查找器的工作就是看是否能找到模塊。這些查找器存放在 sys.meta_path 中以供 Python 遍歷(當然它們也可以通過 import 勾子返回,參見上面的例子)。每個查找器必須實現(xiàn) find_spec 方法。如果一個查找器知道怎么處理將引入的模塊,find_spec 將返回一個 ModuleSpec 對象(參見下節(jié))否則返回 None

          和之前提到的一樣 sys.meta_path 包含三種查找器:

          • 內置模塊查找器
          • 凍結模塊查找器
          • 基于路徑的查找器

          這里我們想重點聊一聊基于路徑的查找器(path based finder)。它用于搜索一系列 import 路徑,每個路徑都用來查找是否有對應的模塊可以加載。默認的路徑查找器實現(xiàn)了所有在文件系統(tǒng)的特殊文件中查找模塊的功能,這些特殊文件包括 Python 源文件(.py 文件),Python 編譯后代碼文件(.pyc 文件),共享庫文件(.so 文件)。如果 Python 標準庫中包含 zipimport,那么相關的文件也可用來查找可引入的模塊。

          路徑查找器不僅限于文件系統(tǒng)中的文件,它還可以上 URL 數(shù)據庫的查詢,或者其他任何可以用字符串表示的地址。

          你可以用上節(jié)提供的勾子來實現(xiàn)對同類型地址的模塊查找。例如,如果你想通過 URL 來 import 模塊,那么你可以寫一個 import 勾子來解析這個 URL 并且返回一個路徑查找器。

          注意,路徑查找器不同于元路徑查找器。后者在 sys.meta_path 中用于被 Python 遍歷,而前者特指基于路徑的查找器。

          五、ModuleSpec 對象

          每個元路徑查找器必須實現(xiàn) find_spec 方法,如果該查找器知道如果處理要引入的模塊,那么這個方法將返回一個 ModuleSpec 對象。這個對象有兩個屬性值得一提,一個是模塊的名字,而另一個則是查找器。如果一個 ModuleSpec 對象的查找器是 None,那么類似 ImportError: missing loader 的異常將會被拋出。查找器將用來創(chuàng)建和執(zhí)行一個模塊(見下節(jié))。

          你可以通過 .__spec__ 來查找模塊的 ModuleSpec 對象:

          In?[1]:?import?sys

          In?[2]:?sys.__spec__
          Out[2]:?ModuleSpec(name='sys',?loader=<class?'_frozen_importlib.BuiltinImporter'>)

          六、加載器(loader)

          加載器通過 create_module 來創(chuàng)建模塊以及 exec_module 來執(zhí)行模塊。通常如果一個模塊是一個 Python 模塊(非內置模塊或者動態(tài)擴展),那么該模塊的代碼需要在模塊的 __dict__ 空間上執(zhí)行。如果模塊的代碼無法執(zhí)行,那么就會拋出ImportError 異常,或者其他在執(zhí)行過程中的異常也會被拋出。

          絕大多數(shù)情況下,查找器和加載器是同一個東西。這種情況下,查找器的 find_spec 方法返回的 ModuleSpec 對象的 loader 屬性將指向它自己。

          我們可以用 create_module 來動態(tài)創(chuàng)建一個模塊,如果它返回 None Python 會自動創(chuàng)建一個模塊。

          七、總結

          Python 的 import 機制靈活而強大。以上的介紹大部分是基于官方文檔,以及較新的 Python 3.6+ 版本。由于篇幅,還有很多細節(jié)并沒有包含其中,例如子模塊的加載、模塊代碼的緩存機制等等。文章中也難免出現(xiàn)紕漏如果有任何問題,

          歡迎到項目開 issue 提問及討論。


          關注公眾號第一時間收到更新

          瀏覽 62
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美韩三级 | 婷婷五月伊人 | 欧美黄片在线免费观看 | 国产精品无码专区AV在线播放 | 影音先锋成人电影在线播放 |