
前言:
如果你跟我一樣,對python的字節碼感興趣,想了解python的代碼在內存中到底是怎么去運行的,那么你可以繼續往下看,如果你是python新手,我建議你移步它處,本文適合有點基礎的python讀者。
如果你不知道怎么生成python的字節碼文件,可以查閱我的
python 代碼反匯編 的博文
python代碼的執行過程:
- python代碼編譯成字節碼【類似於匯編指令的中間語言】
- 字節碼由python虛擬機來執行編譯后的字節碼
說明:
一個python語句會對
應若個字節碼指令,每個字節指令又對應着一個函數偏移量,可以理解為指令的ID
虛擬機一條一條執行字節碼指令,從而完成程序的執行,而dis模塊可以對CPython代碼進行反匯編,生成字節碼指令
dis.dis() 轉化后的字節碼格式如下:
源碼行號 | 指令偏移量 | 指令符號 | 指令參數 | 實際參數值
說明: 不同版本的CPython 指令長度可能不同,但是 3.7的每條指令是2個字節,所以我們去看dis 生成的字節碼指令集的時候,指令偏移量總是從0開始,每增加一條在原來的偏移量上增加2
故,指令偏移量的值,一般都是: 0 , 2 , 4, 6 , 8 , ... , 2n ( n>=0 )
變量指令解析
變量 — _const
LOAD_CONST :加載
const 變量,比如數值,字符串等等, 一般用於傳遞給函數作為參數
案例一:
test(2,'hello')
對應的字節碼指令
1 0 LOAD_NAME 0 (test) 2 LOAD_CONST 0 (2) 4 LOAD_CONST 1 ('hello') 6 CALL_FUNCTION 2 8 POP_TOP 10 LOAD_CONST 2 (None) 12 RETURN_VALUE
局部變量 — _FAST
LOAD_FAST :一般用於加載局部變量的值,也就是讀取值,用於計算或者函數調用傳傳等
STORE_FAST :一般用於保存值到局部變量
案例二:
n = n / p
對應的字節碼指令
1 0 LOAD_NAME 0 (n) 2 LOAD_NAME 1 (p) 4 BINARY_TRUE_DIVIDE 6 STORE_NAME 0 (n) 8 LOAD_CONST 0 (None) 10 RETURN_VALUE
說明: 函數的形參也是局部變量,那么如何區分局部變量中的形參呢?
形參是沒有初始化的,所以如果發現發現操作的一個局部變量只有 LOAD_FAST 而沒有 STORE_FAST,那么這個變量就是形參了。而其它的局部變量在使用之前肯定會使用STORE_FAST進行初始化。
案例三:
def test(arg1): num = 0 print(num, arg1)
對應的字節碼指令
1 0 LOAD_CONST 0 (<code object test at 0x10546c150, file "code.py", line 1>) 2 LOAD_CONST 1 ('test') 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (test) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE Disassembly of <code object test at 0x10546c150, file "code.py", line 1>: 2 0 LOAD_CONST 1 (0) 2 STORE_FAST 1 (num) 3 4 LOAD_GLOBAL 0 (print) 6 LOAD_FAST 1 (num) 8 LOAD_FAST 0 (arg1). #只有LOAD_FAST ,沒有 STORE_FAST 10 CALL_FUNCTION 2 12 POP_TOP 14 LOAD_CONST 0 (None) 16 RETURN_VALUE
全局變量 — _GLOBAL
LOAD_GLOBAL : 用來加載全局變量, 包括制定函數名,類名,模塊名等全局符號
STORE_GLOBAL :用來給全局變量賦值
案例四
def test(arg1): global age age = 20 print(age)
對應的字節碼指令
1 0 LOAD_CONST 0 (<code object test at 0x1056e3150, file "code.py", line 1>) 2 LOAD_CONST 1 ('test') 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (test) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE Disassembly of <code object test at 0x1056e3150, file "code.py", line 1>: 3 0 LOAD_CONST 1 (20) 2 STORE_GLOBAL 0 (age) 4 4 LOAD_GLOBAL 1 (print) 6 LOAD_GLOBAL 0 (age) 8 CALL_FUNCTION 1 10 POP_TOP 12 LOAD_CONST 0 (None) 14 RETURN_VALUE
常用數據類型
1.list
BUILD_LIST : 用於創建一個 list 結構
案例五
a = [1, 2]
對應的字節碼指令
1 0 LOAD_CONST 0 (1) 2 LOAD_CONST 1 (2) 4 BUILD_LIST 2 6 STORE_NAME 0 (a) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE //程序結束
案例六
[ x for x in range(4) if x > 2 ]
對應的字節碼
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x10bffa150, file "code.py", line 1>) 2 LOAD_CONST 1 ('<listcomp>') 4 MAKE_FUNCTION 0 6 LOAD_NAME 0 (range) 8 LOAD_CONST 2 (4) 10 CALL_FUNCTION 1 12 GET_ITER 14 CALL_FUNCTION 1 16 POP_TOP 18 LOAD_CONST 3 (None) 20 RETURN_VALUE Disassembly of <code object <listcomp> at 0x10bffa150, file "code.py", line 1>: 1 0 BUILD_LIST 0 //創建 list , 為賦值給某變量,這種時候一般都是語法糖結構了 2 LOAD_FAST 0 (.0) >> 4 FOR_ITER 16 (to 22) //開啟迭代循環 6 STORE_FAST 1 (x) //局部變量x 8 LOAD_FAST 1 (x) // 導入 x 10 LOAD_CONST 0 (2) // 導入 2 12 COMPARE_OP 4 (>) // x 與 2 進行比較,比較符號為 > 14 POP_JUMP_IF_FALSE 4 // 不滿足條件就跳過 “出棧“ 動作,既,continue 到 " >> 4 FOR_ITER. “ 處 16 LOAD_FAST 1 (x) // 讀取滿足條件的局部變量x 18 LIST_APPEND 2 // 把滿足條件的x 添加到list中 20 JUMP_ABSOLUTE 4 >> 22 RETURN_VALUE //程序結束
2.dict
BUILD_MAP : 用於創建一個空的dict
STORE_MAP : 用於初始化 dict 中的內容,賦值給變量
案例七
k = {'a': 1}
對應的字節碼
1 0 LOAD_CONST 0 ('a') 2 LOAD_CONST 1 (1) 4 BUILD_MAP 1 6 STORE_NAME 0 (k) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE
3.slice
BUILD_SLICE : 用於創建切片, 對於 list , tuple , 字符串都可以使用slice 的方式進行訪問
BINARY_SUBSCR : 讀取slice 的值
STORE_SUBSCR : slice 的值賦值給變量。
案例八
num = [1, 2, 3] a = num[1:2] b = num[0:1:1] num[1:2] = [10, 11]
對應的字節碼
1 0 LOAD_CONST 0 (1) 2 LOAD_CONST 1 (2) 4 LOAD_CONST 2 (3) 6 BUILD_LIST 3 8 STORE_NAME 0 (num) 2 10 LOAD_NAME 0 (num) 12 LOAD_CONST 0 (1) 14 LOAD_CONST 1 (2) 16 BUILD_SLICE 2 #創建了一個切片 18 BINARY_SUBSCR #讀取切片中的值 20 STORE_NAME 1 (a) #將讀取切片中的值賦值給變量 a 3 22 LOAD_NAME 0 (num) 24 LOAD_CONST 3 (0) 26 LOAD_CONST 0 (1) 28 LOAD_CONST 0 (1) 30 BUILD_SLICE 3 32 BINARY_SUBSCR 34 STORE_NAME 2 (b) 4 36 LOAD_CONST 4 (10) 38 LOAD_CONST 5 (11) 40 BUILD_LIST 2 42 LOAD_NAME 0 (num) 44 LOAD_CONST 0 (1) 46 LOAD_CONST 1 (2) 48 BUILD_SLICE 2 50 STORE_SUBSCR 52 LOAD_CONST 6 (None) 54 RETURN_VALUE
4.循環
SETUP_LOOP :用於開始一個循環。
JUMP_ABSOLUTE: 結束循環
案例九
i = 0 while i < 10: i += 1
對應的字節碼
1 0 LOAD_CONST 0 (0) 2 STORE_NAME 0 (i) 2 4 SETUP_LOOP 20 (to 26) // 循環開始處,26表示循環結束點 >> 6 LOAD_NAME 0 (i) // “>>" 表示循環切入點 8 LOAD_CONST 1 (10) 10 COMPARE_OP 0 (<) 12 POP_JUMP_IF_FALSE 24 3 14 LOAD_NAME 0 (i) 16 LOAD_CONST 2 (1) 18 INPLACE_ADD 20 STORE_NAME 0 (i) 22 JUMP_ABSOLUTE 6 // 邏輯上,循環在此處結束 >> 24 POP_BLOCK >> 26 LOAD_CONST 3 (None) 28 RETURN_VALUE
案例十
num = 0 for i in range(5): num += i
對應的字節碼
1 0 LOAD_CONST 0 (0) 2 STORE_NAME 0 (num) 2 4 SETUP_LOOP 24 (to 30) //開始循環 6 LOAD_NAME 1 (range) 8 LOAD_CONST 1 (5) 10 CALL_FUNCTION 1 //調用range 函數 12 GET_ITER //獲取迭代 range 的 iter >> 14 FOR_ITER 12 (to 28) //開始進行 range 的迭代 16 STORE_NAME 2 (i) 3 18 LOAD_NAME 0 (num) 20 LOAD_NAME 2 (i) 22 INPLACE_ADD 24 STORE_NAME 0 (num) 26 JUMP_ABSOLUTE 14 >> 28 POP_BLOCK >> 30 LOAD_CONST 2 (None) 32 RETURN_VALUE
5.if
POP_JUMP_IF_FALSE : 條件結果為 FALSE 則跳出 目標的偏移指令
JUMP_FORWARD : 直接跳轉到目標便宜指令
COMPARE_OP: 比較指令
案例十一
num = 20 if num < 10: print('lt 10') elif num > 10: print('gt 10') else: print('eq 10')
對應的字節碼
1 0 LOAD_CONST 0 (20) 2 STORE_NAME 0 (num) 2 4 LOAD_NAME 0 (num) 6 LOAD_CONST 1 (10) 8 COMPARE_OP 0 (<) 10 POP_JUMP_IF_FALSE 22 3 12 LOAD_NAME 1 (print) 14 LOAD_CONST 2 ('lt 10') 16 CALL_FUNCTION 1 18 POP_TOP 20 JUMP_FORWARD 26 (to 48) 4 >> 22 LOAD_NAME 0 (num) 24 LOAD_CONST 1 (10) 26 COMPARE_OP 4 (>) 28 POP_JUMP_IF_FALSE 40 5 30 LOAD_NAME 1 (print) 32 LOAD_CONST 3 ('gt 10') 34 CALL_FUNCTION 1 36 POP_TOP 38 JUMP_FORWARD 8 (to 48) 7 >> 40 LOAD_NAME 1 (print) 42 LOAD_CONST 4 ('eq 10') 44 CALL_FUNCTION 1 46 POP_TOP >> 48 LOAD_CONST 5 (None) 50 RETURN_VALUE
參考資料: