1.1什么是import機(jī)制?
通常來(lái)講,在一段Python代碼中去執(zhí)行引用另一個(gè)模塊中的代碼,就需要使用Python的import機(jī)制。import語(yǔ)句是觸發(fā)import機(jī)制最常用的手段,但并不是唯一手段。
importlib.import_module和__import__函數(shù)也可以用來(lái)引入其他模塊的代碼。
1.2import是如何執(zhí)行的?
import語(yǔ)句會(huì)執(zhí)行兩步操作:
搜索需要引入的模塊
將模塊的名字做為變量綁定到局部變量中
搜索步驟實(shí)際上是通過(guò)__import__函數(shù)完成的,而其返回值則會(huì)作為變量被綁定到局部變量中。下面我們會(huì)詳細(xì)聊到__import__函數(shù)是如果運(yùn)作的。
二、import機(jī)制概覽
下圖是import機(jī)制的概覽圖。不難看出,當(dāng)import機(jī)制被觸發(fā)時(shí),Python首先會(huì)去sys.modules中查找該模塊是否已經(jīng)被引入過(guò),如果該模塊已經(jīng)被引入了,就直接調(diào)用它,否則再進(jìn)行下一步。這里sys.modules可以看做是一個(gè)緩存容器。值得注意的是,如果sys.modules中對(duì)應(yīng)的值是None那么就會(huì)拋出一個(gè)ModuleNotFoundError異常。下面是一個(gè)簡(jiǎn)單的實(shí)驗(yàn):
In[1]:importsys
In[2]:sys.modules['os']=None
In[3]:importos
---------------------------------------------------------------------------
ModuleNotFoundErrorTraceback(mostrecentcalllast)
in
---->1importos
ModuleNotFoundError:importofoshalted;Noneinsys.modules
如果在sys.modules找到了對(duì)應(yīng)的module,并且這個(gè)import是由import語(yǔ)句觸發(fā)的,那么下一步將對(duì)把對(duì)應(yīng)的變量綁定到局部變量中。
如果沒(méi)有發(fā)現(xiàn)任何緩存,那么系統(tǒng)將進(jìn)行一個(gè)全新的import過(guò)程。在這個(gè)過(guò)程中Python將遍歷sys.meta_path來(lái)尋找是否有符合條件的元路徑查找器(metapathfinder)。sys.meta_path是一個(gè)存放元路徑查找器的列表。它有三個(gè)默認(rèn)的查找器:
內(nèi)置模塊查找器
凍結(jié)模塊(frozenmodule)查找器
基于路徑的模塊查找器。
In[1]:importsys
In[2]:sys.meta_path
Out[2]:
[_frozen_importlib.BuiltinImporter,
_frozen_importlib.FrozenImporter,
_frozen_importlib_external.PathFinder]
查找器的find_spec方法決定了該查找器是否能處理要引入的模塊并返回一個(gè)ModeuleSpec對(duì)象,這個(gè)對(duì)象包含了用來(lái)加載這個(gè)模塊的相關(guān)信息。如果沒(méi)有合適的ModuleSpec對(duì)象返回,那么系統(tǒng)將查看sys.meta_path的下一個(gè)元路徑查找器。如果遍歷sys.meta_path都沒(méi)有找到合適的元路徑查找器,將拋出ModuleNotFoundError。引入一個(gè)不存在的模塊就會(huì)發(fā)生這種情況,因?yàn)閟ys.meta_path中所有的查找器都無(wú)法處理這種情況:
In[1]:importnosuchmodule
---------------------------------------------------------------------------
ModuleNotFoundErrorTraceback(mostrecentcalllast)
in
---->1importnosuchmodule
ModuleNotFoundError:Nomodulenamed'nosuchmodule'
但是,如果這個(gè)手動(dòng)添加一個(gè)可以處理這個(gè)模塊的查找器,那么它也是可以被引入的:
In[1]:importsys
...:
...:fromimportlib.abcimportMetaPathFinder
...:fromimportlib.machineryimportModuleSpec
...:
...:classNoSuchModuleFinder(MetaPathFinder):
...:deffind_spec(self,fullname,path,target=None):
...:returnModuleSpec('nosuchmodule',None)
...:
...:#don'tdothisinyourscript
...:sys.meta_path=[NoSuchModuleFinder()]
...:
...:importnosuchmodule
---------------------------------------------------------------------------
ImportErrorTraceback(mostrecentcalllast)
in
11sys.meta_path=[NoSuchModuleFinder()]
12
--->13importnosuchmodule
ImportError:missingloader
可以看到,當(dāng)我們告訴系統(tǒng)如何去find_spec的時(shí)候,是不會(huì)拋出ModuleNotFound異常的。但是要成功加載一個(gè)模塊,還需要加載器loader。
加載器是ModuleSpec對(duì)象的一個(gè)屬性,它決定了如何加載和執(zhí)行一個(gè)模塊。如果說(shuō)ModuleSpec對(duì)象是“師父領(lǐng)進(jìn)門”的話,那么加載器就是“修行在個(gè)人”了。在加載器中,你完全可以決定如何來(lái)加載以及執(zhí)行一個(gè)模塊。這里的決定,不僅僅是加載和執(zhí)行模塊本身,你甚至可以修改一個(gè)模塊:
In[1]:importsys
...:fromtypesimportModuleType
...:fromimportlib.machineryimportModuleSpec
...:fromimportlib.abcimportMetaPathFinder,Loader
...:
...:classModule(ModuleType):
...:def__init__(self,name):
...:self.x=1
...:self.name=name
...:
...:classExampleLoader(Loader):
...:defcreate_module(self,spec):
...:returnModule(spec.name)
...:
...:defexec_module(self,module):
...:module.y=2
...:
...:classExampleFinder(MetaPathFinder):
...:deffind_spec(self,fullname,path,target=None):
...:returnModuleSpec('module',ExampleLoader())
...:
...:sys.meta_path=[ExampleFinder()]
In[2]:importmodule
In[3]:module
Out[3]:)>
In[4]:module.x
Out[4]:1
In[5]:module.y
Out[5]:2
從上面的例子可以看到,一個(gè)加載器通常有兩個(gè)重要的方法create_module和exec_module需要實(shí)現(xiàn)。如果實(shí)現(xiàn)了exec_module方法,那么create_module則是必須的。如果這個(gè)import機(jī)制是由import語(yǔ)句發(fā)起的,那么create_module方法返回的模塊對(duì)象對(duì)應(yīng)的變量將會(huì)被綁定到當(dāng)前的局部變量中。如果一個(gè)模塊因此成功被加載了,那么它將被緩存到sys.modules。如果這個(gè)模塊再次被加載,那么sys.modules的緩存將會(huì)被直接引用。
以上內(nèi)容為大家介紹了Python的import機(jī)制,希望對(duì)大家有所幫助,如果想要了解更多Python相關(guān)知識(shí),請(qǐng)關(guān)注IT培訓(xùn)機(jī)構(gòu):千鋒教育。