溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

python的import機制如何實現(xiàn)

發(fā)布時間:2022-05-12 11:27:03 來源:億速云 閱讀:174 作者:iii 欄目:開發(fā)技術

本篇內容主要講解“python的import機制如何實現(xiàn)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“python的import機制如何實現(xiàn)”吧!

import 機制功能

Python 的 import 機制基本上可以切分為三個不同的功能:

  • Python運行時的全局模塊池的維護和搜索;

  • 解析與搜索模塊路徑的樹狀結構;

  • 對不同文件格式的模塊執(zhí)行動態(tài)加載機制;

盡管 import 的表現(xiàn)形式千變萬化,但是都可以歸結為:import x.y.z 的形式,當然 import sys 也可以看成是 x.y.z 的一種特殊形式。而諸如 from、as 與 import 的結合,實際上同樣會進行 import x.y.z 的動作,只是最后在當前名字空間中引入的符號各有不同。

然后導入模塊,虛擬機會調用 __import__,那么我們就來看看這個函數(shù)長什么樣子。

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;//表示默認絕對導入
  
    //從PyTupleObject中解析出需要的信息
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "U|OOOi:__import__",
                    kwlist, &name, &globals, &locals, &fromlist, &level))
        return NULL;
    //導入模塊
    return PyImport_ImportModuleLevelObject(name, globals, locals,
                                            fromlist, level);
}

里面有一個PyArg_ParseTupleAndKeywords函數(shù),我們需要提一下,它在虛擬機中是一個被廣泛使用的函數(shù),原型如下:

//Python/getargs.c
int PyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
                                const char *, char **, ...);

這個函數(shù)的作用是參數(shù)解析,負責將 args 和 kwds 中所包含的所有對象(指針)按指定的格式 format 解析成各種目標對象,可以是 Python 的對象,例如 PyListObject、PyLongObject,也可以是 C 的原生對象。

我們知道這個 builtin__import__ 里面的參數(shù) args 指向一個 PyTupleObject ,包含了 import 函數(shù)運行所需要的參數(shù)和信息,它是虛擬機在執(zhí)行 IMPORT_NAME 指令的時候打包產生的。

然而在這里,虛擬機進行了一個逆動作,將打包后的這個 PyTupleObject 拆開,重新獲得當初的參數(shù)。Python 在自身的實現(xiàn)中大量使用了這樣的打包、拆包策略,使得可變數(shù)量的對象能夠很容易地在函數(shù)之間傳遞。

該系列完結后,會介紹如何用 C 給 Python 寫擴展,到時候會剖析這個函數(shù)的用法。

在完成了對參數(shù)的拆包動作之后,會進入 PyImport_ImportModuleLevelObject ,這個我們在 import_name 中已經看到了,當然它內部也是調用了 __import__。

另外每個包和模塊都有一個__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__則是包所在的路徑,對于模塊而言, __path 為空列表。

此外還有一個 file 屬性,對于模塊而言就是其自身的完整路徑;對于包而言則分兩種情況,如果包內部存在 __init__.py 文件,那么得到的就是 __init__.py 文件的完整路徑,沒有則為 None。

下面來看一下不同的導入方式對應的字節(jié)碼,然后在虛擬機的層面來理解這些導入方式。

單模塊導入

以一個簡單的模塊導入為例:

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)在我們已經很清楚地了解了 IMPORT_NAME 的行為。在 IMPORT_NAME 指令的最后,虛擬機會將 PyModuleObject對象(指針)壓入到運行時棧,隨后會將 <"sys", PyModuleObject *> 存放到當前的 local名字空間中。

級聯(lián)導入

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
"""

如果是級聯(lián)導入,那么 IMPORT_NAME 的指令參數(shù)則是完整的路徑信息,該指令的內部將解析這個路徑,并為 sklearn, sklearn.linear_model, sklearn.linear_model.ridge都創(chuàng)建一個 PyModuleObject 對象,這三者都存在于 sys.modules 里面。

但是我們看到 STORE_NAME 是 sklearn,表示只有 sklearn 這個符號暴露在了當前模塊的 local 空間里面??蔀槭裁词莝klearn呢?難道不應該是 sklearn.linear_model.ridge 嗎?

其實經過我們之前的分析這一點已經不再是問題了,因為 import sklearn.linear_model.ridge并不是說導入一個模塊或包叫做 sklearn.linear_model.ridge,而是先導入 sklearn,然后把 linear_model 放在 sklearn 的屬性字典里面,再把 ridge 放在 linear_model 的屬性字典里面。

同理 sklearn.linear_model.ridge 代表的是先從 local 空間里面找到 sklearn,再從 sklearn 的屬性字典中找到 linear_model,然后在 linear_model 的屬性字典里面找到ridge。因為 linear_model 和 ridge 已經在相應的屬性字典里面,我們通過 sklearn 一級一級往下找是可以找到的,因此只需要將符號 skearn 暴露給 local 空間即可。

或者說暴露 sklearn.linear_model.ridge 本身就是不合理的,因為這表示導入一個名字就叫做 sklearn.linear_model.ridge 的模塊或者包,但顯然不存在。而即便我們創(chuàng)建了這樣的一個模塊或包,由于 Python 的語法解析規(guī)范依舊不會得到想要的結果。不然的話,假設 import test_import.a,那是導入名為 test_import.a 的模塊或包呢?還是導入 test_import 下的 a 呢?

也正如我們之前分析的 test_import.a,我們在導入 test_import.a 的時候,會把 test_import 加載進來,然后把 a 加到 test_import 的屬性字典里面,最后只需要把 test_import 返回即可。

因為通過 test_import 可以找到 a,或者說 test_import.a 代表的含義就是從 test_import 的屬性字典里面獲取 a,所以 import test_import.a 必須要返回 test_import,而且只需返回 test_import。

至于 sys.modules 里面雖然存在字符串名為 "test_import.a"的 key 的,但這是為了避免重復加載所采取的策略,它依舊表示從 test_import 的屬性字典里面獲取 a。

import pandas.core

print(pandas.DataFrame({"a": [1, 2, 3]}))
"""
   a
0  1
1  2
2  3
"""
# 所以通過 pandas.DataFrame 是可以調用的

導入 pandas.core 會先導入 pandas,也就是執(zhí)行 pandas 內部的 init 文件。雖然 sys.modules 里面同時有 "pandas" 和 "pandas.core",但是暴露在 local 空間的只有 pandas,所以調用 pandas.DataFrame 是完全合理的。至于 pandas.core 顯然它無法暴露,因為這不符合 Python 的變量命名規(guī)范,變量的名稱里面不能出現(xiàn)小數(shù)點,它只是單純地表示從 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
"""

注意此時的 2 LOAD_CONST 不再是 None 了,而是一個元組,虛擬機將 ridge 放到了當前模塊的 local 空間中。并且 sklearn.linear_model 和 sklearn 都被導入了,存在 sys.modules 里面。

但是 sklearn 卻并不在當前 local 空間中,盡管它被創(chuàng)建了,但是又被隱藏了。IMPORT_NAME 是 sklearn.linear_model,也表示導入 sklearn,然后把 sklearn 下面的 linear_model 加入到 sklearn 的屬性字典里面。

而之所以 sklearn 沒在 local 空間里面,可以這樣理解。當只出現(xiàn) import 的時候,那么我們必須從頭開始一級一級向下調用,所以頂層的包必須加入到 local 空間里面。但這里通過 from ... import ...把 ridge 導出了,此時 ridge 已經指向了 sklearn 下面的 linear_model 下面的 ridge,那么就不需要 sklearn 了,或者說 sklearn 就沒必要暴露在 local 空間里面了,但它確實被導入進來了。

并且 sys.modules 里面也不存在 "ridge"這個key,存在的是 "sklearn.linear_model.ridge",暴露給 local空間的符號是 ridge。

所以正如上面所說,不管什么導入,都可以歸結為 import x.y.z 的形式,只是暴露出來的符號不同罷了。

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
""

這個和上面的 from & import 類似,"sklearn", "sklearn.linear_model", "sklearn.linear_model.ridge" 都在 sys.modules 里面。但是我們加上了 as xxx,那么這個 xxx 就直接指向了 sklearn 下面的 linear_model 下面的 ridge,此時就不需要 sklearn 了。

因此只有 xxx 暴露在了當前模塊的 local空間里面,而 sklearn 雖然也被導入了,但它只在 sys.modules 里面,沒有暴露給當前模塊的 local 空間。

from & import & as

from sklearn.linear_model import ridge as xxx

這個我想連字節(jié)碼都不需要貼了,和之前的 from & import 一樣,只是最后暴露給 local 空間的 ridge 變成了我們自己指定的 xxx。

與module對象有關的名字空間問題

同函數(shù)、類一樣,每個 PyModuleObject 也有自己的名字空間。一個模塊不能直接訪問另一個模塊的內容,盡管模塊內部的作用域比較復雜,比如:遵循 LEGB 規(guī)則,但是模塊與模塊之間的劃分則是很明顯的。

# test1.py
name = "古明地覺"
def print_name():
    return name
# test2.py
from test1 import name, print_name
name = "古明地戀"
print(print_name())  # 古明地覺

執(zhí)行 test2.py 之后,發(fā)現(xiàn)打印的依舊是"古明地覺"。我們說 Python 是根據 LEGB 規(guī)則進行查找,而 print_name 函數(shù)里面沒有 name,那么去外層找。test2.py 里面的 name 是"古明地戀",但是打印的依舊是 test1.py 里面的 "古明地覺"。為什么?

還是那句話,模塊與模塊之間的作用域劃分的非常明顯,print_name 是 test1.py 里面的函數(shù),所以在返回 name 的時候,只會從 test1.py 中搜索,無論如何都是不會跳過test1.py、跑到 test2.py 里面的。

再來看個例子:

# test1.py
name = "古明地覺"
nicknames = ["小五", "少女覺"]
# test2.py
import test1
test1.name = "?古明地覺?"
test1.nicknames = ["覺大人"]
from test1 import name, nicknames
print(name)  # ?古明地覺?
print(nicknames)  # ['覺大人']

此時打印的結果變了,很簡單,這里是直接把 test1 里面的變量修改了。因為這種方式,相當于直接修改 test1 的屬性字典。那么后續(xù)再導入的時候,打印的就是修改之后的值。

# test1.py
name = "古明地覺"
nicknames = ["小五", "少女覺"]
# test2.py
from test1 import name, nicknames
name = "古明地戀"
nicknames.remove("小五")
from test1 import name, nicknames
print(name)  # 古明地覺
print(nicknames)  # ["少女覺"]

如果是 from test1 import name, nicknames,那么相當于在當前的 local空間中新創(chuàng)建變量 name 和 nicknames,它們和 test1 中的 name 和 nicknames 指向相同的對象。

name = "古明地覺" 相當于重新賦值了,所以不會影響test1里的 name;而 nicknames.remove 則是在本地進行修改,所以會產生影響。

到此,相信大家對“python的import機制如何實現(xiàn)”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI