大師兄的Python源碼學習筆記(三十五): 模塊的動態(tài)加載機制(二)

大師兄的Python源碼學習筆記(三十四): 模塊的動態(tài)加載機制(一)
大師兄的Python源碼學習筆記(三十六): 模塊的動態(tài)加載機制(三)

二邦邦、import機制的黑盒探測

  • 從Python語法角度來說,import有多種寫法:
import pandas
import pandas.arrays
from pandas import Dataframe
from pandas import Dataframe as df
from pandas import *
  • 從導入的目標來說逼龟,可以分為系統(tǒng)的標準模塊用戶自己寫的模塊恬汁。
  • 用戶寫的模塊奢方,可以又分為python原生實現(xiàn)的模塊C語言實現(xiàn)并以dll或者so形式存在的模塊
1. 標準import
1.1 Python內(nèi)建Module
  • 以sys模塊為例,查看import對當前名字空間的影響:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>> import sys
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'sys']
>>> type(sys)
<class 'module'>
  • 可以發(fā)現(xiàn)歪沃,在import sys后,名字空間中增加了sys嫌松,而sys對應的是一個module對象沪曙,即源碼中的PyModuleObject對象
  • 根據(jù)前面的章節(jié)萎羔,我們知道Python在初始化的過程中液走,會將一大批module加載到內(nèi)存中,其中也包括sys module。
  • 但為了使local名字空間能夠達到最干凈的效果缘眶,Python并沒有將這些符號暴露在當前的local名字空間中腻窒,而是需要用戶通過import機制通知Python實現(xiàn)這一點。
  • 這些預先被加載進內(nèi)存的module存放在sys.module中:
demo.py

>>>import sys

>>>def show_modules():
>>>    for item in sys.modules.items():
>>>        print(item)

>>>show_modules()
('sys', <module 'sys' (built-in)>)
('builtins', <module 'builtins' (built-in)>)
('_frozen_importlib', <module 'importlib._bootstrap' (frozen)>)
('_imp', <module '_imp' (built-in)>)
('_warnings', <module '_warnings' (built-in)>)
('_frozen_importlib_external', <module 'importlib._bootstrap_external' (frozen)>)
('_io', <module 'io' (built-in)>)
... ...
  • 如果模擬os模塊import到local名字空間的過程:
demo.py

>>> import sys
>>> id(sys.modules['os'])
1755873529264
>>> import os
>>> id(os)
1755873529264
  • 可以看出手動導入和import的id是一樣的磅崭,綜上所述儿子,可以證明類似sys module這樣的內(nèi)建module是從sys.modules中導入的。
1.2 用戶自定義Module
  • 在Python中砸喻,用戶可以通過.py文件創(chuàng)建自己的module柔逼,也可以通過C語言創(chuàng)建.dll或.so生成擴展module,這些都不是Python的內(nèi)建module割岛。
  • 建立一個簡單的案例:
test.py

>>>a=1
>>>b=2
demo.py 

>>>import sys

>>>def test_in_modules():
>>>    return print("test" in sys.modules.keys())

>>>test_in_modules()
False
>>>import test
>>>test_in_modules()
True
>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test', 'test_in_modules']
>>>print(id(test))
2018381998256
>>>print(id(sys.modules['test']))
2018381998256
>>>print(type(test))
<class 'module'>
  • 根據(jù)代碼結果愉适,Python通過import機制創(chuàng)建了一個新的module,將其引入到local名字空間中癣漆,并且還將其加載到sys.module中维咸。
  • 由于id相同,表示其在local名字空間和sys.module中背后對應的是同一個PyModuleObject對象惠爽。
  • 進一步探索test內(nèi)部:
>>>import test
>>>print(dir(test))
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']
>>>print(dir(test.__dict__.keys()))
['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'isdisjoint']
>>>print(test.__name__)
test
>>>print(test.__file__)
.\test.py
  • 可以看出module對象內(nèi)部實際上是通過一個dict維護所有的屬性和屬性值癌蓖。
  • 所以同class一樣,module是一個名字空間婚肆。
  • 如果這時查看目錄租副,可以發(fā)現(xiàn)在import過程中,Python在__pycache__文件夾下生成了用于儲存編譯結果的test.pyc文件较性。
  • 觀察__builtins__符號:
demo.py

>>>import test
>>>print(type(__builtins__))
<class 'module'>
>>>print(id(__builtins__))
1761693991696
>>>print(type(test.__builtins__))
<class 'dict'>
>>>print(id(test.__builtins__))
1761693989184
  • 可以看出當前l(fā)ocal名字空間中的__builtins__和test中的__builtins__雖然名字一樣用僧,但一個是module對象,一個是dict赞咙,且id不同责循,所以并不是同一個東西。
  • 再深挖__builtins__
demo.py

import test
>>>print(id(test.__builtins__))
2325670918464
>>>print(id(__builtins__.__dict__))
2325670918464
>>>print(id(sys.modules['builtins'].__dict__))
2325670918464
  • 可以發(fā)現(xiàn)test.__builtins__對應的dict正是當前l(fā)ocal名字空間中的__builtins__對應的module對象所維護的那個dict對象攀操。
  • 而其實它們兩個都只是表象院仿,它們背后的真身實際上就是我們在對Python運行環(huán)境初始化分析中看到的那個__builtin__ module以及它所維護的dict
  • 這個__builtin__ module和其它被Python預先加載到內(nèi)存的module一樣崔赌,維護在sys.modules中意蛀。
2. 嵌套import
  • 首先建立一個嵌套import案例:
test1.py

import sys
test2.py

import test1
demo.py

>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']
>>>import test2
>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test2']
>>>print(dir(test2))
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'test1']
>>>print(dir(test2.test1))
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']
  • 可以發(fā)現(xiàn),test1和test2中進行的import動作并沒有影響到上一層名字空間健芭,而只影響了各個module自身的名字空間县钥,也就是module自身維護的dict對象
  • 但確實會影響到全局module集合
demo.py

>>>import test2
>>>print(sys.modules['test2'])
<module 'test2' from 'D:\\pythonProject\\parser_learn\\test2.py'>
  • 所有的import動作慈迈,不論發(fā)生在任何時間和位置若贮,都會影響到全局module集合誓沸。
  • 這樣的好處是當程序重復import模塊時合武,Python虛擬機只需要返回全局module集合中緩存的那個module對象即可:
demo.py

>>>import test2,test1

>>>print(id(test1))
2555034952864
>>>print(id(test2.test1))
2555034952864
3. import package
  • 在Python中芹敌,package(包)用于管理多個module(模塊)慢洋,一個package通常就是一個目錄。
  • 多個小package也可以聚合成一個較大的package匾效,多個module舷蟀、package最終組織成一個樹形結構,從而為最初散亂的class建立起一種方便管理面哼、維護和用戶試用的結構,以xml package為例:
mypackage.test.py

>>>a=1
>>>b=2
  • 在Python2中野宜,如果要成為一個package,則在目錄下必須有一個文件__init__.py魔策,在Python3中則沒有這么嚴格匈子,但是如果想調(diào)用package中的模塊,還是需要先在__init__.py中定義闯袒。
demo.py

import mypackage

>>>print(mypackage)
<module 'mypackage' (namespace)>
>>>print(mypackage.test)
Traceback (most recent call last):
  File "D:/demo.py", line 4, in <module>
    print(mypackage.test)
AttributeError: module 'mypackage' has no attribute 'test'
  • 增加__init__.py后:
mypackage.__init__.py

from . import test
demo.py 

import mypackage

>>>print(mypackage)
<module 'mypackage' from 'D:\\mypackage\\__init__.py'>
>>>print(mypackage.test)
<module 'mypackage.test' from 'D:\\mypackage\\test.py'>
  • 可以看出python導入一個包虎敦,會先執(zhí)行這個包的__init__文件。
  • 再深入分析導入的結果:
demo.py

>>>import sys
>>>import mypackage.test

>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'mypackage', 'sys']
>>>import mypackage
>>>print(dir(mypackage))
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'test']
>>>print(id(mypackage.test))
2378910617488
>>>print(id(sys.modules['mypackage.test']))
2378910617488
  • 可以看出在導入mypackage.test時政敢,實際連mypackage一起加載到名字空間了其徙,這說明在Python中,packagemodule之間的區(qū)別并不是那么僵硬堕仔,package也可以像module一樣唄加載擂橘,行為和module也是一樣的。
  • 對于test的訪問必須通過mypackage.test來實現(xiàn)的好處摩骨,是避免在不同名字空間中產(chǎn)生名字沖突,這和C++中的namespace和Java中的package機制是一樣的朗若。
  • 至于為什么會在加載mypackage.test時同時也加載mypackage恼五,是因為對于test module的引用只能通過mypackage.test來實現(xiàn),Python會首先在當前的local名字空間中查找mypackage對應的對象哭懈,然后再在該對象的屬性集合(名字空間)中查找test灾馒。
  • 但如果在同一個package中有多個module時,加載其中一個module并不會加載其它module
mypackage.test1

>>>a=1
>>>b=2
mypackage.test2

>>>c=3
>>>d=4
demo.py

>>>import mypackage.test1
>>>print(dir(mypackage))
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'test1']
>>>import mypackage.test2
>>>print(dir(mypackage))
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'test1', 'test2']
4. from 與 import
  • 通過from關鍵字與import結合遣总,可以實現(xiàn)精準控制加載對象睬罗,只將我們期望的module,甚至是module中的某個符號動態(tài)加載到內(nèi)存中旭斥,避免名字空間遭到污染容达。
demo.py 

>>>import sys
>>>from mypackage import test1
>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test1']
>>>print(sys.modules['mypackage.test1'])
<module 'mypackage.test1' from 'D:\\mypackage\\test1.py'>
  • 這種方式本質上與import mypackage.test1是一樣的,都是將package mypackage和module mypackage.test1動態(tài)加載到了sys.modules集合中垂券。
  • 不同之處在于當import動作要結束時花盐,Python會在當前的local名字空間中引入什么符號:

import mypackage.test1中,Python虛擬機引入了符號mypackage,并將其映射到module mypackage算芯。
from mypackage import test1中柒昏,Python虛擬機引入了符號test,并將其映射到module mypackage.test熙揍。

  • 對于fromimport的結合职祷,還有一種更精妙的用法,可以加載module的某個部分:
demo.py

>>>import sys
>>>from mypackage.test1 import a
>>>print(a)
1
>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'sys']
>>>print(sys.modules['mypackage.test1'])
<module 'mypackage.test1' from 'D:\\mypackage\\test1.py'>
  • 除此之外届囚,Python還提供了一種機制堪旧,允許將一個module中的所有對象一次性地引入到當前名字空間中:
demo.py

>>>from mypackage.test1 import *
>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']
5. 符號重命名
  • Python通過關鍵字as提供了一種符號重命名機制,為動態(tài)加載機制提供了更大的靈活性奖亚。
  • 通過as可以控制module以什么名字被引入到當前的local名字空間中:
demo.py

>>>import sys
>>>import mypackage.test1 as test

>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test']
>>>print(sys.modules['mypackage.test1'])
<module 'mypackage.test1' from 'D:\\mypackage\\test1.py'>
  • 可以看出淳梦,在上面代碼中,test實際是被映射到module mypackage.test1昔字。
6. 符號的銷毀與重載
  • 模塊使用之后也可能會刪除爆袍,原因可能是釋放內(nèi)存或給名字空間瘦身等。
  • 在python中作郭,通常刪除一個對象可以使用del關鍵字:
demo.py

>>>import sys
>>>import mypackage.test1 as test

>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys', 'test']
>>>del test
>>>print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']
>>>print(sys.modules['mypackage.test1'])
<module 'mypackage.test1' from 'D:\\pythonProject\\parser_learn\\mypackage\\test1.py'>
  • 可以看出陨囊,del過后,test確實被從名字空間中刪除了夹攒,但是module mypackage.test1依然在系統(tǒng)中蜘醋,他沒有被刪除,只是被隱藏起來了咏尝。
  • 之所以要采取這種類似module pool的緩存機制压语,是因為組成一個完整系統(tǒng)的多個py文件可能都會對某個module進行import動作,希望使用這個module所提供的的功能编检。
  • 從Python的角度看胎食,import其實并不完全等同于我們鎖熟知的動態(tài)加載概念,它的真實含義是希望某個module能夠被感知允懂,就是將module以某個符號的形式引入到某個名字空間中厕怜。
  • 所以Python引入了全局module集合sys.modules,這個集合為modules pool,保存了module的唯一映像蕾总,當某個py文件通過import聲明希望感知某個module時粥航,如果已經(jīng)在pool中,則引用一個符號到該py文件的名字空間中生百,并關聯(lián)到該module递雀;如果pool中不存在該module才會執(zhí)行動態(tài)加載動作。
  • 假如在加載了module后置侍,module本身被更新映之,則需要使用builtin module中的reload操縱實現(xiàn):
demo.py

>>> import importlib,sys
>>> import mypackage.test1 as test
>>> id(test)
2543593826720
>>> sys.modules['mypackage.test1']
<module 'mypackage.test1' from 'D:\\mypackage\\test1.py'>
>>> dir(sys.modules['mypackage.test1'])
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']
>>> importlib.reload(test) # 這里module test發(fā)生了變化
<module 'mypackage.test1' from 'D:\\mypackage\\test1.py'>
>>> dir(sys.modules['mypackage.test1'])
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b', 'c']
>>> id(test)
2543593826720
  • 可以看出拦焚,經(jīng)過reload后,module確實更新了杠输,但是id并沒有變化赎败,所以Python并沒有創(chuàng)建新的module對象。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蠢甲,一起剝皮案震驚了整個濱河市僵刮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鹦牛,老刑警劉巖搞糕,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異曼追,居然都是意外死亡窍仰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門礼殊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驹吮,“玉大人,你說我怎么就攤上這事晶伦〉” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵婚陪,是天一觀的道長族沃。 經(jīng)常有香客問我,道長泌参,這世上最難降的妖魔是什么脆淹? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮及舍,結果婚禮上未辆,老公的妹妹穿的比我還像新娘。我一直安慰自己锯玛,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布兼蜈。 她就那樣靜靜地躺著攘残,像睡著了一般。 火紅的嫁衣襯著肌膚如雪为狸。 梳的紋絲不亂的頭發(fā)上歼郭,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音辐棒,去河邊找鬼病曾。 笑死牍蜂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的泰涂。 我是一名探鬼主播鲫竞,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逼蒙!你這毒婦竟也來了从绘?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤是牢,失蹤者是張志新(化名)和其女友劉穎僵井,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驳棱,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡批什,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了社搅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驻债。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖罚渐,靈堂內(nèi)的尸體忽然破棺而出却汉,到底是詐尸還是另有隱情,我是刑警寧澤荷并,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布合砂,位于F島的核電站,受9級特大地震影響源织,放射性物質發(fā)生泄漏翩伪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一谈息、第九天 我趴在偏房一處隱蔽的房頂上張望缘屹。 院中可真熱鬧,春花似錦侠仇、人聲如沸轻姿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽互亮。三九已至,卻和暖如春余素,著一層夾襖步出監(jiān)牢的瞬間豹休,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工桨吊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留威根,地道東北人凤巨。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像洛搀,于是被迫代替她去往敵國和親敢茁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

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