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

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

1 如何導(dǎo)入模塊

首先來看一個導(dǎo)入模塊的例子橱健。創(chuàng)建一個文件夾demo5而钞,文件夾中有如下幾個文件。

ssj@ssj-mbp ~/demo5 $ ls
__init__.py math.py     sys.py      test.py

根據(jù)python規(guī)則拘荡,因為文件夾下面有init.py文件臼节,因此demo5是一個包。各個文件內(nèi)容如下:

#__init__.py
import sys
import math

#math.py
print 'my math'

#sys.py
print 'my sys'

#test.py
import sys
import math

好了,問題來了网缝,當(dāng)我在demo5目錄運行python test.py的時候巨税,會打印什么結(jié)果呢?sys模塊和math模塊會調(diào)用demo5目錄下面的還是系統(tǒng)本身的模塊呢粉臊?結(jié)果是只打印出了my math草添,也就是說,sys模塊并不是導(dǎo)入的demo5目錄下面的sys模塊扼仲。但是远寸,如果我們不是直接運行test.py,而是導(dǎo)入整個包呢屠凶?結(jié)果大為不同驰后,當(dāng)我們在demo5上層目錄執(zhí)行import demo5時,可以發(fā)現(xiàn)打印出了my sysmy math矗愧,也就是說灶芝,導(dǎo)入的都是demo5目錄下面的兩個模塊。出現(xiàn)這兩個不同結(jié)果就是python模塊和包導(dǎo)入機制導(dǎo)致的唉韭。下面來分析下python模塊和包導(dǎo)入機制夜涕。

2 Python模塊和包導(dǎo)入原理

python模塊和包導(dǎo)入函數(shù)調(diào)用路徑是builtin___import__->import_module_level->load_next->import_submodule->find_module->load_module,本文不打算分析所有的函數(shù)纽哥,只摘出幾處關(guān)鍵代碼分析钠乏。

builtin___import__函數(shù)解析import參數(shù),比如import xxxfrom yyy import xxx解析后獲取的參數(shù)是不一樣的春塌。然后通過import_module_level函數(shù)解析模塊和包的樹狀結(jié)構(gòu)晓避,并調(diào)用load_next來導(dǎo)入模塊。而load_next調(diào)用import_submodule來查找并導(dǎo)入模塊只壳。注意到如果是從包里面導(dǎo)入模塊的話俏拱,load_next先用包含包名的完整模塊名調(diào)用import_submodule來尋找并導(dǎo)入模塊,如果找不到吼句,則只用模塊名來尋找并導(dǎo)入模塊锅必。import_submodule會先根據(jù)模塊完整名fullname來判斷是否是系統(tǒng)模塊,即之前說過的sys.modules是否有該模塊惕艳,比如sys搞隐,os等模塊,如果是系統(tǒng)模塊远搪,則直接返回對應(yīng)模塊劣纲。否則根據(jù)模塊路徑調(diào)用find_module搜索模塊并調(diào)用load_module函數(shù)導(dǎo)入模塊。注意到如果不是從包中導(dǎo)入模塊谁鳍,find_module中會判斷模塊是否是內(nèi)置模塊或者擴展模塊(注意到這里的內(nèi)置模塊和擴展模塊是指不常用的系統(tǒng)模塊癞季,比如imp和math模塊等)劫瞳,如果是則直接初始化該內(nèi)置模塊并加入到之前的備份模塊集合extensions中。否則需要先后搜索模塊包的路徑和系統(tǒng)默認路徑是否有該模塊绷柒,如果都沒有搜索到該模塊志于,則報錯。找到了模塊废睦,則初始化模塊并將模塊引用加入到sys.modules中伺绽。

load_module這個函數(shù)需要額外說明下,該函數(shù)會根據(jù)模塊類型不同來使用不同的加載方式郊楣,基本類型有PY_SOURCE, PY_COMPILED,C_BUILTIN, C_EXTENSION,PKG_DIRECTORY等憔恳。PY_SOURCE指的就是普通的py文件,而PY_COMPILED則是指編譯后的pyc文件净蚤,如果py文件和pyc文件都存在钥组,則這里的類型為PY_SOURCE,你可能會有點納悶了,這樣豈不是影響效率了么今瀑?其實不然程梦,這是為了保證導(dǎo)入的是最新的模塊代碼,因為在load_source_module中會判斷pyc文件是否過時橘荠,如果沒有過時屿附,還是會在這里導(dǎo)入pyc文件的,所以性能上并不會有太多影響哥童。而C_BUILTIN指的是系統(tǒng)內(nèi)置模塊挺份,比如imp模塊,C_EXTENSION指的是擴展模塊贮懈,通常是以動態(tài)鏈接庫形式存在的匀泊,比如math.so模塊。PKG_DIRECTORY則是指導(dǎo)入的是包朵你,比如導(dǎo)入demo5包各聘,會先導(dǎo)入包demo5本身,然后導(dǎo)入init.py模塊抡医。

/*load_next函數(shù)部分代碼*/
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;
}

接下來就需要解釋下第一節(jié)中提出的問題了躲因,首先直接python test.py的時候,那么先后導(dǎo)入sys模塊和math模塊忌傻,由于是直接導(dǎo)入模塊大脉,則全名就是sys,在導(dǎo)入sys模塊的時候水孩,雖然當(dāng)前目錄下有sys模塊镰矿,但是sys模塊是系統(tǒng)模塊,所以會在import_submodule中直接返回系統(tǒng)的sys模塊荷愕。而math模塊不是系統(tǒng)預(yù)先加載的模塊衡怀,所以會在當(dāng)前目錄下找到并加載。

而如果使用了包機制安疗,我們import demo5時抛杨,則此時會先加載demo5包本身,然后加載__init__.py模塊荐类,init.py中會加載sys和math模塊怖现,由于是通過包來加載,所以fullname會變成demo5.sys和demo5.math玉罐。顯然在判斷的時候屈嗤,demo5.sys不在系統(tǒng)預(yù)先加載的模塊sys.modules中,因此最終會加載當(dāng)前目錄下面的sys模塊吊输。math則跟前面情況類似饶号。

3 模塊和名字空間

在導(dǎo)入模塊的時候,會在名字空間中引入對應(yīng)的名字季蚂。注意到導(dǎo)入模塊和設(shè)置的名字空間的名字時不一樣的茫船,需要注意區(qū)分下。下面給個栗子扭屁,這里有個包foobar算谈,里面有a.py, b.py,__init__.py

In [1]: import sys

In [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 foobar
import package foobar

In [4]: sys.modules['foobar']
Out[4]: <module 'foobar' from 'foobar/__init__.pyc'>

In [5]: import foobar.a
import module a

In [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 b
import module b

In [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'

我們知道料滥,導(dǎo)入的模塊都會加入到sys.modules字典中然眼。當(dāng)我們導(dǎo)入模塊的時候,可以簡單分為以下幾種情況葵腹,具體原理可以參見源碼:

  • import foobar.a
    這是直接導(dǎo)入模塊a高每,那么在sys.modules中存在foobar和foobar.a,但是在local名字空間中只存在foobar礁蔗,并沒有foobar.a觉义。這是由import機制決定的,在導(dǎo)入模塊的代碼中可以看到針對foobar.a最終存儲到名字空間的只有foobar浴井。
  • from foobar import b
    這種情況存儲到sys.modules的也只有foobar(前面已經(jīng)導(dǎo)入不會重復(fù)導(dǎo)入了)和foobar.b晒骇。local名字空間只有b,沒有foobar磺浙,也沒有foobar.b洪囤。
  • import foobar.a as A
    這種情況sys.modules中還是foobar和foobar.a,而local名字空間只有A撕氧,沒有foobar瘤缩,更沒有foobar.a。如果我們執(zhí)行del A刪除符號A伦泥,則名字空間不在有符號A剥啤,但是在sys.modules中還是存在foobar和foobar.a的锦溪。

4 其他

需要提到的一點是,如果我們修改了某個py文件府怯,然后reload該模塊刻诊,則刪除的符號并不會更新,而只是會加入新增加的符號或者更新已經(jīng)有的符號牺丙。比如下面這個例子则涯,我們加入 b = 2后reload模塊reloadtest,可以看到模塊中多了符號b冲簿,而我們刪除b = 2加入c=3后粟判,發(fā)現(xiàn)符號b還是在模塊reloadtest中,并沒有刪除峦剔。這是python內(nèi)部reload機制決定的档礁,在reload操作時,python內(nèi)部實現(xiàn)是找到原模塊的字典吝沫,并更新或添加符號事秀,并不刪除原有的符號。

#初始代碼 a = 1
In [1]: import reloadtest 

In [2]: import sys

In [3]: dir(sys.modules['reloadtest'])
Out[3]: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a']

##新增一行代碼 b = 2 
In [4]: reload(reloadtest)
Out[4]: <module 'reloadtest' from 'reloadtest.py'>

In [5]: dir(sys.modules['reloadtest'])
Out[5]: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b']

##刪除代碼 b = 2野舶,新增 c = 3
In [6]: reload(reloadtest)
Out[6]: <module 'reloadtest' from 'reloadtest.py'>

In [7]: dir(sys.modules['reloadtest'])
Out[7]: 
['__builtins__',
 '__doc__',
 '__file__',
 '__name__',
 '__package__',
 'a',
 'b',
 'c']
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末易迹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子平道,更是在濱河造成了極大的恐慌睹欲,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件一屋,死亡現(xiàn)場離奇詭異窘疮,居然都是意外死亡,警方通過查閱死者的電腦和手機冀墨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門闸衫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诽嘉,你說我怎么就攤上這事蔚出。” “怎么了虫腋?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵骄酗,是天一觀的道長。 經(jīng)常有香客問我悦冀,道長趋翻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任盒蟆,我火速辦了婚禮踏烙,結(jié)果婚禮上师骗,老公的妹妹穿的比我還像新娘。我一直安慰自己讨惩,他們只是感情好丧凤,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著步脓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浩螺。 梳的紋絲不亂的頭發(fā)上靴患,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音要出,去河邊找鬼鸳君。 笑死,一個胖子當(dāng)著我的面吹牛患蹂,可吹牛的內(nèi)容都是我干的或颊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼传于,長吁一口氣:“原來是場噩夢啊……” “哼囱挑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沼溜,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤平挑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后系草,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體通熄,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年找都,在試婚紗的時候發(fā)現(xiàn)自己被綠了唇辨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡能耻,死狀恐怖赏枚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晓猛,我是刑警寧澤嗡贺,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站鞍帝,受9級特大地震影響诫睬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜帕涌,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一摄凡、第九天 我趴在偏房一處隱蔽的房頂上張望续徽。 院中可真熱鬧,春花似錦亲澡、人聲如沸钦扭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽客情。三九已至,卻和暖如春癞己,著一層夾襖步出監(jiān)牢的瞬間膀斋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工痹雅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留仰担,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓绩社,卻偏偏與公主長得像摔蓝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子愉耙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1.1Python中的模塊介紹和使用 有過C語言編程經(jīng)驗的朋友都知道在C語言中如果要引用sqrt函數(shù)贮尉,必須用語句#...
    TENG書閱讀 416評論 0 0
  • 在Python中有一個概念叫做模塊(module),這個和C語言中的頭文件以及Java中的包很類似朴沿,比如在Pyth...
    一只寫程序的猿閱讀 3,988評論 0 3
  • 1. 標(biāo)準(zhǔn) import Python 中所有加載到內(nèi)存的模塊都放在 sys.modules 绘盟。當(dāng) import ...
    唐文閣閱讀 1,740評論 0 1
  • mac mini入手幾天(配傳統(tǒng)鍵盤鼠標(biāo))網(wǎng)上找里很多使用技巧,太多了悯仙,不好記住龄毡,而且不一定用得上,現(xiàn)在就將使用過...
    Isme閱讀 5,480評論 1 2
  • 周一锡垄,新的開始沦零,早上7點到校,吃早飯货岭,7點10分上早自習(xí)路操,各科課代表齊作業(yè),我也開始了我的行動千贯,讓政治課代表郭曉雪...
    9f450160aee6閱讀 306評論 0 1