python的import?機(jī)制是怎么實(shí)現(xiàn)的
import 機(jī)制功能
Python 的 import 機(jī)制基本上可以切分為三個(gè)不同的功能:
- Python運(yùn)行時(shí)的全局模塊池的維護(hù)和搜索;
- 解析與搜索模塊路徑的樹狀結(jié)構(gòu);
- 對(duì)不同文件格式的模塊執(zhí)行動(dòng)態(tài)加載機(jī)制;
盡管 import 的表現(xiàn)形式千變?nèi)f化,但是都可以歸結(jié)為:import x.y.z 的形式,當(dāng)然 import sys 也可以看成是 x.y.z 的一種特殊形式。而諸如 from、as 與 import 的結(jié)合,實(shí)際上同樣會(huì)進(jìn)行 import x.y.z 的動(dòng)作,只是最后在當(dāng)前名字空間中引入的符號(hào)各有不同。
然后導(dǎo)入模塊,虛擬機(jī)會(huì)調(diào)用 __import__,那么我們就來(lái)看看這個(gè)函數(shù)長(zhǎng)什么樣子。
static PyObject *
builtin___import__(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"name", "globals", "locals", "fromlist",
"level", 0};
//初始化globals、fromlist都為NULL
PyObject *name, *globals = NULL, *locals = NULL, *fromlist = NULL;
int level = 0;//表示默認(rèn)絕對(duì)導(dǎo)入
//從PyTupleObject中解析出需要的信息
if (!PyArg_ParseTupleAndKeywords(args, kwds, "U|OOOi:__import__",
kwlist, &name, &globals, &locals, &fromlist, &level))
return NULL;
//導(dǎo)入模塊
return PyImport_ImportModuleLevelObject(name, globals, locals,
fromlist, level);
}里面有一個(gè)PyArg_ParseTupleAndKeywords函數(shù),我們需要提一下,它在虛擬機(jī)中是一個(gè)被廣泛使用的函數(shù),原型如下:
//Python/getargs.c
int PyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
const char *, char **, ...);這個(gè)函數(shù)的作用是參數(shù)解析,負(fù)責(zé)將 args 和 kwds 中所包含的所有對(duì)象(指針)按指定的格式 format 解析成各種目標(biāo)對(duì)象,可以是 Python 的對(duì)象,例如 PyListObject、PyLongObject,也可以是 C 的原生對(duì)象。
我們知道這個(gè) builtin__import__ 里面的參數(shù) args 指向一個(gè) PyTupleObject ,包含了 import 函數(shù)運(yùn)行所需要的參數(shù)和信息,它是虛擬機(jī)在執(zhí)行 IMPORT_NAME 指令的時(shí)候打包產(chǎn)生的。
然而在這里,虛擬機(jī)進(jìn)行了一個(gè)逆動(dòng)作,將打包后的這個(gè) PyTupleObject 拆開,重新獲得當(dāng)初的參數(shù)。Python 在自身的實(shí)現(xiàn)中大量使用了這樣的打包、拆包策略,使得可變數(shù)量的對(duì)象能夠很容易地在函數(shù)之間傳遞。
該系列完結(jié)后,會(huì)介紹如何用 C 給 Python 寫擴(kuò)展,到時(shí)候會(huì)剖析這個(gè)函數(shù)的用法。
在完成了對(duì)參數(shù)的拆包動(dòng)作之后,會(huì)進(jìn)入 PyImport_ImportModuleLevelObject ,這個(gè)我們?cè)?import_name 中已經(jīng)看到了,當(dāng)然它內(nèi)部也是調(diào)用了 __import__。
另外每個(gè)包和模塊都有一個(gè)__name__和__path__屬性。
import numpy as np import numpy.core import six print(np.__name__, np.__path__) """ numpy ['C:\\python38\\lib\\site-packages\\numpy'] """ print(np.core.__name__, np.core.__path__) """ numpy.core ['C:\\python38\\lib\\site-packages\\numpy\\core'] """ print(six.__name__, six.__path__) """ six [] """
name__就是模塊名或者包名,如果是包下面的包或者模塊,那么就是包名.包名或者包名.模塊名;至于__path__則是包所在的路徑,對(duì)于模塊而言, __path 為空列表。
此外還有一個(gè) file 屬性,對(duì)于模塊而言就是其自身的完整路徑;對(duì)于包而言則分兩種情況,如果包內(nèi)部存在 __init__.py 文件,那么得到的就是 __init__.py 文件的完整路徑,沒(méi)有則為 None。
下面來(lái)看一下不同的導(dǎo)入方式對(duì)應(yīng)的字節(jié)碼,然后在虛擬機(jī)的層面來(lái)理解這些導(dǎo)入方式。
單模塊導(dǎo)入
以一個(gè)簡(jiǎn)單的模塊導(dǎo)入為例:
import sys """ 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (sys) 6 STORE_NAME 0 (sys) 8 LOAD_CONST 1 (None) 10 RETURN_VALUE """
這是我們一開始考察的例子,現(xiàn)在我們已經(jīng)很清楚地了解了 IMPORT_NAME 的行為。在 IMPORT_NAME 指令的最后,虛擬機(jī)會(huì)將 PyModuleObject對(duì)象(指針)壓入到運(yùn)行時(shí)棧,隨后會(huì)將 <"sys", PyModuleObject *> 存放到當(dāng)前的 local名字空間中。
級(jí)聯(lián)導(dǎo)入
import sklearn.linear_model.ridge """ 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (sklearn.linear_model.ridge) 6 STORE_NAME 1 (sklearn) 8 LOAD_CONST 1 (None) 10 RETURN_VALUE """
如果是級(jí)聯(lián)導(dǎo)入,那么 IMPORT_NAME 的指令參數(shù)則是完整的路徑信息,該指令的內(nèi)部將解析這個(gè)路徑,并為 sklearn, sklearn.linear_model, sklearn.linear_model.ridge都創(chuàng)建一個(gè) PyModuleObject 對(duì)象,這三者都存在于 sys.modules 里面。
但是我們看到 STORE_NAME 是 sklearn,表示只有 sklearn 這個(gè)符號(hào)暴露在了當(dāng)前模塊的 local 空間里面??蔀槭裁词莝klearn呢?難道不應(yīng)該是 sklearn.linear_model.ridge 嗎?
其實(shí)經(jīng)過(guò)我們之前的分析這一點(diǎn)已經(jīng)不再是問(wèn)題了,因?yàn)?import sklearn.linear_model.ridge并不是說(shuō)導(dǎo)入一個(gè)模塊或包叫做 sklearn.linear_model.ridge,而是先導(dǎo)入 sklearn,然后把 linear_model 放在 sklearn 的屬性字典里面,再把 ridge 放在 linear_model 的屬性字典里面。
同理 sklearn.linear_model.ridge 代表的是先從 local 空間里面找到 sklearn,再?gòu)?sklearn 的屬性字典中找到 linear_model,然后在 linear_model 的屬性字典里面找到ridge。因?yàn)?linear_model 和 ridge 已經(jīng)在相應(yīng)的屬性字典里面,我們通過(guò) sklearn 一級(jí)一級(jí)往下找是可以找到的,因此只需要將符號(hào) skearn 暴露給 local 空間即可。
或者說(shuō)暴露 sklearn.linear_model.ridge 本身就是不合理的,因?yàn)檫@表示導(dǎo)入一個(gè)名字就叫做 sklearn.linear_model.ridge 的模塊或者包,但顯然不存在。而即便我們創(chuàng)建了這樣的一個(gè)模塊或包,由于 Python 的語(yǔ)法解析規(guī)范依舊不會(huì)得到想要的結(jié)果。不然的話,假設(shè) import test_import.a,那是導(dǎo)入名為 test_import.a 的模塊或包呢?還是導(dǎo)入 test_import 下的 a 呢?
也正如我們之前分析的 test_import.a,我們?cè)趯?dǎo)入 test_import.a 的時(shí)候,會(huì)把 test_import 加載進(jìn)來(lái),然后把 a 加到 test_import 的屬性字典里面,最后只需要把 test_import 返回即可。
因?yàn)橥ㄟ^(guò) test_import 可以找到 a,或者說(shuō) test_import.a 代表的含義就是從 test_import 的屬性字典里面獲取 a,所以 import test_import.a 必須要返回 test_import,而且只需返回 test_import。
至于 sys.modules 里面雖然存在字符串名為 "test_import.a"的 key 的,但這是為了避免重復(fù)加載所采取的策略,它依舊表示從 test_import 的屬性字典里面獲取 a。
import pandas.core
print(pandas.DataFrame({"a": [1, 2, 3]}))
"""
a
0 1
1 2
2 3
"""
# 所以通過(guò) pandas.DataFrame 是可以調(diào)用的導(dǎo)入 pandas.core 會(huì)先導(dǎo)入 pandas,也就是執(zhí)行 pandas 內(nèi)部的 init 文件。雖然 sys.modules 里面同時(shí)有 "pandas" 和 "pandas.core",但是暴露在 local 空間的只有 pandas,所以調(diào)用 pandas.DataFrame 是完全合理的。至于 pandas.core 顯然它無(wú)法暴露,因?yàn)檫@不符合 Python 的變量命名規(guī)范,變量的名稱里面不能出現(xiàn)小數(shù)點(diǎn),它只是單純地表示從 pandas 的屬性字典中加載 core。
from & import
from sklearn.linear_model import ridge
"""
0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('ridge',))
4 IMPORT_NAME 0 (sklearn.linear_model)
6 IMPORT_FROM 1 (ridge)
8 STORE_NAME 1 (ridge)
10 POP_TOP
12 LOAD_CONST 2 (None)
14 RETURN_VALUE
"""注意此時(shí)的 2 LOAD_CONST 不再是 None 了,而是一個(gè)元組,虛擬機(jī)將 ridge 放到了當(dāng)前模塊的 local 空間中。并且 sklearn.linear_model 和 sklearn 都被導(dǎo)入了,存在 sys.modules 里面。
但是 sklearn 卻并不在當(dāng)前 local 空間中,盡管它被創(chuàng)建了,但是又被隱藏了。IMPORT_NAME 是 sklearn.linear_model,也表示導(dǎo)入 sklearn,然后把 sklearn 下面的 linear_model 加入到 sklearn 的屬性字典里面。
而之所以 sklearn 沒(méi)在 local 空間里面,可以這樣理解。當(dāng)只出現(xiàn) import 的時(shí)候,那么我們必須從頭開始一級(jí)一級(jí)向下調(diào)用,所以頂層的包必須加入到 local 空間里面。但這里通過(guò) from ... import ...把 ridge 導(dǎo)出了,此時(shí) ridge 已經(jīng)指向了 sklearn 下面的 linear_model 下面的 ridge,那么就不需要 sklearn 了,或者說(shuō) sklearn 就沒(méi)必要暴露在 local 空間里面了,但它確實(shí)被導(dǎo)入進(jìn)來(lái)了。
并且 sys.modules 里面也不存在 "ridge"這個(gè)key,存在的是 "sklearn.linear_model.ridge",暴露給 local空間的符號(hào)是 ridge。
所以正如上面所說(shuō),不管什么導(dǎo)入,都可以歸結(jié)為 import x.y.z 的形式,只是暴露出來(lái)的符號(hào)不同罷了。
import & as
import sklearn.linear_model.ridge as xxx """ 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (sklearn.linear_model.ridge) 6 IMPORT_FROM 1 (linear_model) 8 ROT_TWO 10 POP_TOP 12 IMPORT_FROM 2 (ridge) 14 STORE_NAME 3 (xxx) 16 POP_TOP 18 LOAD_CONST 1 (None) 20 RETURN_VALUE ""
這個(gè)和上面的 from & import 類似,"sklearn", "sklearn.linear_model", "sklearn.linear_model.ridge" 都在 sys.modules 里面。但是我們加上了 as xxx,那么這個(gè) xxx 就直接指向了 sklearn 下面的 linear_model 下面的 ridge,此時(shí)就不需要 sklearn 了。
因此只有 xxx 暴露在了當(dāng)前模塊的 local空間里面,而 sklearn 雖然也被導(dǎo)入了,但它只在 sys.modules 里面,沒(méi)有暴露給當(dāng)前模塊的 local 空間。
from & import & as
from sklearn.linear_model import ridge as xxx
這個(gè)我想連字節(jié)碼都不需要貼了,和之前的 from & import 一樣,只是最后暴露給 local 空間的 ridge 變成了我們自己指定的 xxx。
與module對(duì)象有關(guān)的名字空間問(wèn)題
同函數(shù)、類一樣,每個(gè) PyModuleObject 也有自己的名字空間。一個(gè)模塊不能直接訪問(wèn)另一個(gè)模塊的內(nèi)容,盡管模塊內(nèi)部的作用域比較復(fù)雜,比如:遵循 LEGB 規(guī)則,但是模塊與模塊之間的劃分則是很明顯的。
# test1.py
name = "古明地覺(jué)"
def print_name():
return name
# test2.py
from test1 import name, print_name
name = "古明地戀"
print(print_name()) # 古明地覺(jué)執(zhí)行 test2.py 之后,發(fā)現(xiàn)打印的依舊是"古明地覺(jué)"。我們說(shuō) Python 是根據(jù) LEGB 規(guī)則進(jìn)行查找,而 print_name 函數(shù)里面沒(méi)有 name,那么去外層找。test2.py 里面的 name 是"古明地戀",但是打印的依舊是 test1.py 里面的 "古明地覺(jué)"。為什么?
還是那句話,模塊與模塊之間的作用域劃分的非常明顯,print_name 是 test1.py 里面的函數(shù),所以在返回 name 的時(shí)候,只會(huì)從 test1.py 中搜索,無(wú)論如何都是不會(huì)跳過(guò)test1.py、跑到 test2.py 里面的。
再來(lái)看個(gè)例子:
# test1.py name = "古明地覺(jué)" nicknames = ["小五", "少女覺(jué)"] # test2.py import test1 test1.name = "?古明地覺(jué)?" test1.nicknames = ["覺(jué)大人"] from test1 import name, nicknames print(name) # ?古明地覺(jué)? print(nicknames) # ['覺(jué)大人']
此時(shí)打印的結(jié)果變了,很簡(jiǎn)單,這里是直接把 test1 里面的變量修改了。因?yàn)檫@種方式,相當(dāng)于直接修改 test1 的屬性字典。那么后續(xù)再導(dǎo)入的時(shí)候,打印的就是修改之后的值。
# test1.py
name = "古明地覺(jué)"
nicknames = ["小五", "少女覺(jué)"]
# test2.py
from test1 import name, nicknames
name = "古明地戀"
nicknames.remove("小五")
from test1 import name, nicknames
print(name) # 古明地覺(jué)
print(nicknames) # ["少女覺(jué)"]如果是 from test1 import name, nicknames,那么相當(dāng)于在當(dāng)前的 local空間中新創(chuàng)建變量 name 和 nicknames,它們和 test1 中的 name 和 nicknames 指向相同的對(duì)象。
name = "古明地覺(jué)" 相當(dāng)于重新賦值了,所以不會(huì)影響test1里的 name;而 nicknames.remove 則是在本地進(jìn)行修改,所以會(huì)產(chǎn)生影響。
小結(jié)
以上就是模塊(包)相關(guān)的內(nèi)容,雖然一個(gè)項(xiàng)目可以有很多個(gè)文件,但是每個(gè)文件的執(zhí)行原理是一致的。無(wú)論一個(gè)文件是作為模塊被導(dǎo)入,還是直接作為啟動(dòng)文件被執(zhí)行,虛擬機(jī)的執(zhí)行流程都沒(méi)有變化。
通過(guò)模塊和包,我們便可以對(duì)項(xiàng)目進(jìn)行功能上的劃分,從而更好地組織項(xiàng)目。
到此這篇關(guān)于python的import 機(jī)制是怎么實(shí)現(xiàn)的的文章就介紹到這了,更多相關(guān)import 機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python虛擬環(huán)境virtualenv的安裝與使用
virtualenv用于創(chuàng)建獨(dú)立的Python環(huán)境,多個(gè)Python相互獨(dú)立,互不影響,它能夠:1. 在沒(méi)有權(quán)限的情況下安裝新套件 2. 不同應(yīng)用可以使用不同的套件版本 3. 套件升級(jí)不影響其他應(yīng)用2017-09-09
Python接口自動(dòng)化?之用例讀取方法總結(jié)
這篇文章主要介紹了Python接口自動(dòng)化?之用例讀取方法總結(jié),在軟件測(cè)試中,為項(xiàng)目編寫接口自動(dòng)化用例已成為測(cè)試人員常駐的測(cè)試工作。本文以python為例,基于筆者曾使用過(guò)的三種用例數(shù)據(jù)讀取方法:xlrd、pandas、yaml,下面簡(jiǎn)要地介紹下它們的使用方法及簡(jiǎn)單分析2022-06-06
Django接收post前端返回的json格式數(shù)據(jù)代碼實(shí)現(xiàn)
這篇文章主要介紹了Django接收post前端返回的json格式數(shù)據(jù)代碼實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
python學(xué)習(xí)手冊(cè)中的python多態(tài)示例代碼
多態(tài)是面向?qū)ο笳Z(yǔ)言的一個(gè)基本特性,多態(tài)意味著變量并不知道引用的對(duì)象是什么,根據(jù)引用對(duì)象的不同表現(xiàn)不同的行為方式,下面使用一個(gè)示例學(xué)習(xí)他的使用方法2014-01-01
python filecmp.dircmp實(shí)現(xiàn)遞歸比對(duì)兩個(gè)目錄的方法
這篇文章主要介紹了python filecmp.dircmp實(shí)現(xiàn)遞歸比對(duì)兩個(gè)目錄的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
Python析構(gòu)函數(shù)__del__定義原理解析
這篇文章主要介紹了Python析構(gòu)函數(shù)__del__定義原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
python實(shí)現(xiàn)自動(dòng)生成SQL語(yǔ)句
在數(shù)據(jù)處理和管理中,SQL(Structured?Query?Language)是一種非常重要的語(yǔ)言,本文主要介紹了如何使用python實(shí)現(xiàn)自動(dòng)生成SQL語(yǔ)句,需要的可以參考下2024-04-04
基于Python和MoviePy實(shí)現(xiàn)照片管理和視頻合成工具
在這篇博客中,我們將詳細(xì)剖析一個(gè)基于 Python 的圖形界面應(yīng)用程序,該程序使用 wxPython 構(gòu)建用戶界面,并結(jié)合 MoviePy、Pillow 和 OpenCV 等庫(kù)實(shí)現(xiàn)照片管理和視頻合成功能,通過(guò)對(duì)代碼的逐部分分析,需要的朋友可以參考下2025-04-04
Python?計(jì)算機(jī)視覺(jué)編程進(jìn)階之圖像特效處理篇
計(jì)算機(jī)視覺(jué)這種技術(shù)可以將靜止圖像或視頻數(shù)據(jù)轉(zhuǎn)換為一種決策或新的表示。所有這樣的轉(zhuǎn)換都是為了完成某種特定的目的而進(jìn)行的,本篇我們來(lái)學(xué)習(xí)下如何對(duì)圖像進(jìn)行特效處理2021-11-11

