Python源碼剖析筆記5-模組機制

來源:互聯網
上載者:User

標籤:python   源碼   import   匯入   

本文簡書地址: http://www.jianshu.com/p/14586ec50ab6

python中經常用到模組,比如import xxx,from xxx import yyy這樣子,裡面的機制也是需要好好探究一下的,這次主要從黑盒角度來探測模組機制,源碼分析點到為止,詳盡的源碼分析見陳儒大神的《python源碼剖析》第14章。

1 如何匯入模組

首先來看一個匯入模組的例子。建立一個檔案夾demo5,檔案夾中有如下幾個檔案。

[email protected] ~/demo5 $ ls__init__.py math.py     sys.py      test.py

根據python規則,因為檔案夾下面有init.py檔案,因此demo5是一個包。各個檔案內容如下:

#__init__.pyimport sysimport math#math.pyprint ‘my math‘#sys.pyprint ‘my sys‘#test.pyimport sysimport math

好了,問題來了,當我在demo5目錄運行python test.py的時候,會列印什麼結果呢?sys模組和math模組會調用demo5目錄下面的還是系統本身的模組呢?結果是只列印出了my math,也就是說,sys模組並不是匯入的demo5目錄下面的sys模組。但是,如果我們不是直接運行test.py,而是匯入整個包呢?結果大為不同,當我們在demo5上層目錄執行import demo5時,可以發現列印出了my sysmy math,也就是說,匯入的都是demo5目錄下面的兩個模組。出現這兩個不同結果就是python模組和包匯入機制導致的。下面來分析下python模組和包匯入機制。

2 Python模組和包匯入原理

python模組和包匯入函數調用路徑是builtin___import__->import_module_level->load_next->import_submodule->find_module->load_module,本文不打算分析所有的函數,只摘出幾處關鍵程式碼分析。

builtin___import__函數解析import參數,比如import xxxfrom yyy import xxx解析後擷取的參數是不一樣的。然後通過import_module_level函數解析模組和包的樹狀結構,並調用load_next來匯入模組。而load_next調用import_submodule來尋找並匯入模組。注意到如果是從包裡面匯入模組的話,load_next先用包含包名的完整模組名調用import_submodule來尋找並匯入模組,如果找不到,則只用模組名來尋找並匯入模組。import_submodule會先根據模組完整名fullname來判斷是否是系統模組,即之前說過的sys.modules是否有該模組,比如sys,os等模組,如果是系統模組,則直接返回對應模組。否則根據模組路徑調用find_module搜尋模組並調用load_module函數匯入模組。注意到如果不是從包中匯入模組,find_module中會判斷模組是否是內建模組或者擴充模組(注意到這裡的內建模組和擴充模組是指不常用的系統模組,比如imp和math模組等),如果是則直接初始化該內建模組並加入到之前的備份模組集合extensions中。否則需要先後搜尋模組包的路徑和系統預設路徑是否有該模組,如果都沒有搜尋到該模組,則報錯。找到了模組,則初始化模組並將模組引用加入到sys.modules中。

load_module這個函數需要額外說明下,該函數會根據模組類型不同來使用不同的載入方式,基本類型有PY_SOURCE, PY_COMPILED,C_BUILTIN, C_EXTENSION,PKG_DIRECTORY等。PY_SOURCE指的就是普通的py檔案,而PY_COMPILED則是指編譯後的pyc檔案,如果py檔案和pyc檔案都存在,則這裡的類型為PY_SOURCE,你可能會有點納悶了,這樣豈不是影響效率了嗎?其實不然,這是為了保證匯入的是最新的模組代碼,因為在load_source_module中會判斷pyc檔案是否過時,如果沒有過時,還是會在這裡匯入pyc檔案的,所以效能上並不會有太多影響。而C_BUILTIN指的是系統內建模組,比如imp模組,C_EXTENSION指的是擴充模組,通常是以動態連結程式庫形式存在的,比如math.so模組。PKG_DIRECTORY則是指匯入的是包,比如匯入demo5包,會先匯入包demo5本身,然後匯入init.py模組。

/*load_next函數部分代碼*/static PyObject *load_next() {    .......    result = import_submodule(mod, p, buf); //p是模組名,buf是包含包名的完整模組名    if (result == Py_None && altmod != mod) {        result = import_submodule(altmod, p, p);    }    .......}
/*import_submodule部分代碼*/static PyObject *import_submodule(PyObject *mod, char *subname, char *fullname){    PyObject *modules = PyImport_GetModuleDict();    PyObject *m = NULL;    if ((m = PyDict_GetItemString(modules, fullname)) != NULL) {        Py_INCREF(m);    }    else {                ......        if (mod == Py_None)            path = NULL;        else {            path = PyObject_GetAttrString(mod, "__path__");                        ......        }        .......        fdp = find_module(fullname, subname, path, buf, MAXPATHLEN+1,                  &fp, &loader);        .......        m = load_module(fullname, fp, buf, fdp->type, loader);                .......        if (!add_submodule(mod, m, fullname, subname, modules)) {            Py_XDECREF(m);            m = NULL;        }    }    return m;}

接下來就需要解釋下第一節中提出的問題了,首先直接python test.py的時候,那麼先後匯入sys模組和math模組,由於是直接匯入模組,則全名就是sys,在匯入sys模組的時候,雖然目前的目錄下有sys模組,但是sys模組是系統模組,所以會在import_submodule中直接返回系統的sys模組。而math模組不是系統積極式載入的模組,所以會在目前的目錄下找到並載入。

而如果使用了包機制,我們import demo5時,則此時會先載入demo5包本身,然後載入__init__.py模組,init.py中會載入sys和math模組,由於是通過包來載入,所以fullname會變成demo5.sys和demo5.math。顯然在判斷的時候,demo5.sys不在系統積極式載入的模組sys.modules中,因此最終會載入目前的目錄下面的sys模組。math則跟前面情況類似。

3 模組和名字空間

在匯入模組的時候,會在名字空間中引入對應的名字。注意到匯入模組和設定的名字空間的名字時不一樣的,需要注意區分下。下面給個栗子,這裡有個包foobar,裡面有a.py, b.py,__init__.py

In [1]: import sysIn [2]: sys.modules[‘foobar‘]---------------------------------------------------------------------------KeyError                                  Traceback (most recent call last)<ipython-input-2-9001cd5d540a> in <module>()----> 1 sys.modules[‘foobar‘]KeyError: ‘foobar‘In [3]: import foobarimport package foobarIn [4]: sys.modules[‘foobar‘]Out[4]: <module ‘foobar‘ from ‘foobar/__init__.pyc‘>In [5]: import foobar.aimport module aIn [6]: sys.modules[‘foobar.a‘]Out[6]: <module ‘foobar.a‘ from ‘foobar/a.pyc‘>In [7]: locals()[‘foobar‘]Out[7]: <module ‘foobar‘ from ‘foobar/__init__.pyc‘>In [8]: locals()[‘foobar.a‘]---------------------------------------------------------------------------KeyError                                  Traceback (most recent call last)<ipython-input-8-059690e6961a> in <module>()----> 1 locals()[‘foobar.a‘]KeyError: ‘foobar.a‘In [9]: from foobar import bimport module bIn [10]: locals()[‘b‘]Out[10]: <module ‘foobar.b‘ from ‘foobar/b.pyc‘>In [11]: sys.modules[‘foobar.b‘]Out[11]: <module ‘foobar.b‘ from ‘foobar/b.pyc‘>In [12]: sys.modules[‘b‘]---------------------------------------------------------------------------KeyError                                  Traceback (most recent call last)<ipython-input-13-1df8d2911c99> in <module>()----> 1 sys.modules[‘b‘]KeyError: ‘b‘

我們知道,匯入的模組都會加入到sys.modules字典中。當我們匯入模組的時候,可以簡單分為以下幾種情況,具體原理可以參見源碼:
- import foobar.a
這是直接匯入模組a,那麼在sys.modules中存在foobar和foobar.a,但是在local名字空間中只存在foobar,並沒有foobar.a。這是由import機制決定的,在匯入模組的代碼中可以看到針對foobar.a最終儲存到名字空間的只有foobar。
- from foobar import b
這種情況儲存到sys.modules的也只有foobar(前面已經匯入不會重複匯入了)和foobar.b。local名字空間只有b,沒有foobar,也沒有foobar.b。
- import foobar.a as A
這種情況sys.modules中還是foobar和foobar.a,而local名字空間只有A,沒有foobar,更沒有foobar.a。

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

Python源碼剖析筆記5-模組機制

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.