1. 程式人生 > >Python 匯入機制

Python 匯入機制

Python import hook可以翻譯為Python 探針。
它的實現原理涉及了以下幾個知識點:
1. Python匯入協議
2. sys.meta_path

一,Python匯入協議
Python 中所有載入到記憶體的模組都放在 sys.modules。當import 一個模 塊時首先會在這個列表中查詢是否已經載入了此模組,如果載入了則 只是將模 塊的名字加入到正在呼叫 import 的模組的 Local 名字空間中。如果沒有載入則 從sys.path 目錄中按照模組名稱查詢模組檔案,模組檔案可以是 py、pyc、pyd,找到後將模組載入記憶體,並加入到 sys.modules 中,一般如果 指定模組沒在 sys.modules 中找到,將呼叫Python 的匯入協議來查詢和載入 模組。
python 匯入協議機制是由查詢器finder和載入器loader構成。


查詢器finder的職責是找到指定的模組,然後提供一個載入器來處理實際的匯入 行為。要註冊一個查詢器只需要在 sys.path_hooks 列表中。查詢器不真正載入 模組。如果他們能夠找到指定的模組,他們返回一個模組分支,即模組匯入相 關資訊的封裝,在載入模組時匯入機制運用的資訊。
查詢器物件finder需要實現如下find_module 方法:
finder.find_module(fullname, path=None)1
• 如果 finder 被裝入了 meta_path,這個方法會接受第二個引數,
• 如果 Import 的是頂層模組:None,
• 如果 import 的是子模組或子包:PATH
• 如果找到模組需要返回一個 loader 物件,沒找到需要返回 None
載入器loader需要實現如下load_module 方法

loader.load_module(fullname):
• 這個函式返回一個被載入的模組或者丟擲異常
• 判斷傳入的 fullname 是不是已經在 sys.modules 中了,如果在的話loader 必須使用已經載入的模組,否則 reload 功能不能正常工作,如果fullname 不在 sys.modules 中,loader 必須新建一個 module 並載入
• 在 Loader 執行匯入的模組程式碼之前,模組必須已經被匯入 sys.modules中,否則會引起無窮迭代
• 如果匯入失敗,loader 必須清除已經插入到 sys.modules 中的模組
通過自己實現這樣的包括查詢器和載入器鉤子程式能夠實現對 python 匯入行為的控制,擴充套件Python載入模組的功能。

二。sys.meta_path
python 模組是通過import的方式引用的,當我們執行一行 from package import module as mymodule 命令時,Python直譯器會查詢package這個包的module模組,並將該模組作為mymodule引入到當前的工作空間。import語句主要是做了二件事: 查詢相應的module 和載入module到local namespace 。
在import的第一個階段,主要是完成了查詢要引入模組的功能,這個查詢的過程如下
1. 檢查 sys.modules列表 (儲存了之前import的類庫的快取),如果module被找到,則⾛到第二步。
2. 檢查 sys.meta_path列表。meta_path 是一個 list,⾥面儲存著一些 finder 物件,如果找到該module的話,就會返回一個finder物件。
3. 檢查某些隱式的finder物件,不同的python實現有不同的隱式finder,但是都會有 sys.path_hooks, sys.path_importer_cache 以及sys.path。
4. 丟擲 ImportError
從上面的過程中可以看出,當執行 import 相關的操作時,會觸發 sys.meta_path 列表中定義的finder物件。按照Python匯入協議的規則,我們需要實現一個import hook,只需要實現一個finder物件和loader物件對應的find_module方法和load_module方法。要讓Python直譯器import 模組的 時候觸發這個finder物件,只需要將這個物件的例項插入到sys.metapath列表中。

下面是一個簡單的hellp world程式 我們通過import hook實現在載入模組時 列印查詢和載入的資訊:

import sys

class MetaPathFinder:

    def find_module(self, fullname, path=None):
        print('find_module {}'.format(fullname))
        return MetaPathLoader()


class MetaPathLoader:

    def load_module(self, fullname):
        print('load_module {}'.format(fullname))
        sys.modules[fullname] = sys
        return sys

sys.meta_path.insert(0, MetaPathFinder())

if __name__ == '__main__':
    import http
    print(http)
    print(http.version_info)

load_module 方法返回一個 module 物件,這個物件就是 import 的 module 物件了。 比如我上面那樣就把 http 替換為 sys 這個 module 了。

$ python meta_path1.py
find_module http
load_module http
<module 'sys' (built-in)>
sys.version_info(major=3, minor=5, micro=1, releaselevel='final', serial=0)