學編程這么久了,大家不知道有沒有想過一個問題,當我們執(zhí)行Python
時,它是怎么實現(xiàn)的呢?
眾所周知,Python 是一門解釋型的語言
——所謂“解釋型”,當然是區(qū)別于以 C語言 為代表的編譯型語言。編譯型語言需要將整個程序文件全部轉換為可以直接由機器執(zhí)行的二進制文件;而解釋型語言則是由相應的解釋器一行一行“解釋”并執(zhí)行代碼描述的行為。
正是因此,對于新接觸的人來說,Python
這樣的解釋性語言很多時候需要執(zhí)行到相應的語句,才會發(fā)現(xiàn)一些顯然的錯誤。
話說回來,Python
的解釋器是怎么樣來“解釋”Python
代碼的呢?
實際上,類似于Java
的執(zhí)行機制,Python
也擁有自己的虛擬機。而這個虛擬機實際上執(zhí)行的也是一種“字節(jié)碼”。
在Python
程序的執(zhí)行中依然存在一個“編譯”的過程:將Python
代碼編譯為字節(jié)碼。
并且,Python
也提供了一個名為dis
模塊,用于查看、分析Python
的字節(jié)碼。
1. dis模塊
舉例來說,dis
模塊中有一個同名函數(shù)dis
,可以用于將當前命名空間中的對象反匯編為字節(jié)碼。
import dis
def add(add_1, add_2):
sum_value = add_1 + add_2
dis.dis(add)
執(zhí)行結果為:
4 0 LOAD_FAST 0 (add_1)
2 LOAD_FAST 1 (add_2)
4 BINARY_ADD
6 STORE_FAST 2 (sum_value)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
其中,開頭的數(shù)字“4”表示字節(jié)碼的內容對應于腳本中第 4 行的內容。
隨后的一列數(shù)字則表示對應指令所在的地址。縱向觀察可以發(fā)現(xiàn)一個規(guī)律:下一條指令的地址總比上一條指令的地址大 2 。這是巧合嗎?
顯然不是的。官方文檔《dis --- Python 字節(jié)碼反匯編器》中記錄的更改顯示,從Python 3.6
版本開始,”每條指令使用2個字節(jié)“。所以每條指令的地址會在上一條指令地址的基礎上加2。
再往后,是一列表示指令含義的單詞組合,實際上就是人類可讀的對應指令名稱。顧名思義,LOAD_FAST
就是加載某個內容/對象到某處,”FAST“很可能意味著這是一個便捷快速的命令實現(xiàn)。
最右邊,則是對應于當前命令的操作數(shù),即操作對象。數(shù)字同樣是一個類似于地址的表示,括號中的字符串則表示相應對象在Python代碼中的具體名稱。
這樣我們就可以大概地閱讀生成的字節(jié)碼了:
首先Python將函數(shù)add
的第一個參數(shù)add_1
加載到某處,緊跟著將第二個參數(shù)add_2
加載到第一個參數(shù)之后。然后調用了一個名為BINARY_ADD
的指令,即對之前加載的兩個參數(shù)做加法。再然后則是將加法所得的和sum_value
存儲在了另一個位置。最后,加載了一個常量None
并返回。
其實讀完上面這個執(zhí)行過程,我們很容易想到一種常用的數(shù)據結構——棧。
像下面這樣:
當然這并不是本文的重點——真要探討Python
的實現(xiàn)機制,還得另外寫幾篇長文才能說得一二。
使用dis.dis
函數(shù)除了可以查看當前腳本中各個對象對應的字節(jié)碼,還可以直接傳入一段代碼對應的字符串進行反匯編:
# test_dis.py
import dis
s = """
def add(add_1, add_2):
sum_value = add_1 + add_2
print("Hello World!")
import sys
"""
dis.dis(s)
匯編結果:
2 0 LOAD_CONST 0 (<code object add at 0x0000019FF66DFDB0, file "<dis>", line 2>)
2 LOAD_CONST 1 ('add')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (add)
5 8 LOAD_NAME 1 (print)
10 LOAD_CONST 2 ('Hello World!')
12 CALL_FUNCTION 1
14 POP_TOP
7 16 LOAD_CONST 3 (0)
18 LOAD_CONST 4 (None)
20 IMPORT_NAME 2 (sys)
22 STORE_NAME 2 (sys)
24 LOAD_CONST 4 (None)
26 RETURN_VALUE
2. compile函數(shù)
除了在程序中直接給出要反匯編的程序形成的字符串,我們還可以通過使用內置函數(shù)compile
來形成相應腳本的編譯對象,再使用dis.dis
查看其字節(jié)碼內容。
# test_compile.py
import dis
with open("test_dis.py", "r", encoding="utf-8") as f:
s = f.read()
compile_obj = compile(s, "test_dis.py","exec")
dis.dis(compile_obj)
字節(jié)碼輸出結果:
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (dis)
6 STORE_NAME 0 (dis)
11 8 LOAD_CONST 2 ('\ndef add(add_1, add_2):\n sum_value = add_1 + add_2\n\nprint("Hello World!")\n\nimport sys\n')
10 STORE_NAME 1 (s)
13 12 LOAD_NAME 0 (dis)
14 LOAD_METHOD 0 (dis)
16 LOAD_NAME 1 (s)
18 CALL_METHOD 1
20 POP_TOP
22 LOAD_CONST 1 (None)
24 RETURN_VALUE
總結
dis
模塊為我們提供了一個觀察Python
內部機制的手段,恰當?shù)厥褂?code>dis模塊,并結合其他方法,可以快速有效弄懂一些Python
令人迷惑的地方。
(推薦微課:python3基礎微課)
希望大家善于利用這樣一些有用的工具。
文章來源:公眾號--Python技術 作者:軒轅御龍
以上就是W3Cschool編程獅
關于 當你使用print時,Python是怎么運行的 的相關介紹了,希望對大家有所幫助。