在學習 Python 程式的過程中,我們經常會遇到一些難以理解的錯誤訊息或程式碼行為。此時,如果能夠深入了解 Python 程式的內部機制,將有助於我們更快地解決問題。
而透過 dis
模組,我們可以查看程式的指令構成,去深入瞭解我們所寫的Python程式碼是被如何編譯執行的,或是從中找到可以優化的部分。
摘要
dis
套件的目的:將程式進行反組譯(Disassemble),以人類適合閱讀的形式展現- 常見用途
- 了解Python內部機制
- 調整優化程式效率
基本用法
dis.dis
- 直接 import
dis
模組 - 使用
dis.dis(code)
來分析程式片段
import dis
dis.dis("my_dict = {'a': 1}")
dis 模組
- 在命令行使用
-m dis
來分析整個程式
python -m dis py_file.py
範例
範例1: 基本反組譯
原始程式
my_dict = {'a': 1}
dis輸出結果
0 0 RESUME 0
1 2 LOAD_CONST 0 ('a')
4 LOAD_CONST 1 (1)
6 BUILD_MAP 1
8 STORE_NAME 0 (my_dict)
10 LOAD_CONST 2 (None)
12 RETURN_VALUE
欄位說明
- 第1欄的
0
和1
: 表示目前位於程式原始碼的第N行 - 第2欄的
0
,2
, …,10
,12
:指令碼的地址偏移量- 表示每條指令在於整條函數中的偏移位置
- 通常每個指令會佔用2個字節,但並非一定
- 指令本身佔用一個字節,表示操作碼
- 參數會根據數量佔用不同的字節量,可能會有一個、多個或沒有參數
- 第3欄為指令名稱(opname)
-
具體實現可以在 CPython 的
ceval.c
中找到
-
- 第4欄為參數
- 第5欄為參數解釋
結果解釋
RESUME 0
: Python3.6後新增的操作碼,主要用來控制協程(coroutines)或異步生成器(asynchronous generators)。LOAD_CONST 0 ('a')
- 將一個常數值加入到常數表(常數池)
- 把它放到到記憶體堆疊頂部
- 將一個常數值加入到常數表(常數池)
LOAD_CONST 1 (1)
- 再將一個常數值加入到常數表
- 此時常數表索引0為
'a'
索引1為1
BUILD_MAP 1
- 建立一個字典(dict)
- 並且取出1對key-value來建立字典
STORE_NAME 0 (my_dict)
- 把記憶體頂部的值儲存到變數名稱
my_dict
中
- 把記憶體頂部的值儲存到變數名稱
LOAD_CONST 2 (None)
- 因為沒有指定回傳值,需要從常數表中取出一個None來回傳
RETURN_VALUE
- 將堆疊頂部的值回傳(此處為
None
)
- 將堆疊頂部的值回傳(此處為
範例2: 反組譯函數
原始程式
"""
範例2: 用 dis.dis 來反組譯一個函數
"""
def f(x):
if x % 2 == 0:
return True
else:
return False
import dis
dis.dis(f)
dis輸出結果
4 0 RESUME 0
5 2 LOAD_FAST 0 (x)
4 LOAD_CONST 1 (2)
6 BINARY_OP 6 (%)
10 LOAD_CONST 2 (0)
12 COMPARE_OP 2 (==)
18 POP_JUMP_FORWARD_IF_FALSE 2 (to 24)
6 20 LOAD_CONST 3 (True)
22 RETURN_VALUE
8 >> 24 LOAD_CONST 4 (False)
26 RETURN_VALUE
欄位解釋:
>>
: 表示跳轉分支- 通常會有
POP_JUMP_FORWARD_IF_FALSE
指向它
- 通常會有
結果解釋
- 第5行:
if x % 2 == 0
LOAD_FAST 0 (x)
:將變數x送入堆疊LOAD_CONST 1 (2)
: 將常數1送入堆疊BINARY_OP 6 (%)
: 執行二元運算中的mod運算(6)- 佔4個Byte,1操作碼 + 1指定mod + 2比較對象
LOAD_CONST 2 (0)
:將常數0送入堆疊COMPARE_OP 2 (==)
:比較堆疊2個值是否向燈POP_JUMP_FORWARD_IF_FALSE 2 (to 24)
: 如果堆疊不相等,跳到偏移量24的位置
- 6:
return True
LOAD_CONST 3 (True)
: 從常數表中取出索引為3的值(此處為True)RETURN_VALUE
:回傳
- 8
return False
LOAD_CONST 4 (False)
: 從常數表中取出索引為3的值(此處為False)RETURN_VALUE
延伸:使用dis.code查看常數表
dis.code(f)
Name: f
Filename: /Users/owo/Code/python_practice_note/Python/dis_2.py
Argument count: 1
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals: 1
Stack size: 2
Flags: OPTIMIZED, NEWLOCALS
Constants:
0: None
1: 2
2: 0
3: True
4: False
Variable names:
0: x
範例3: 反組譯類型
原始程式
"""
範例3: 使用 dis 反組譯一個類別
"""
class MyClass:
"""Some description"""
def __init__(self, x):
self.x = x
def f(self):
return self.x
import dis
dis.dis(MyClass)
dis輸出結果
Disassembly of __init__:
7 0 RESUME 0
8 2 LOAD_FAST 1 (x)
4 LOAD_FAST 0 (self)
6 STORE_ATTR 0 (x)
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
Disassembly of f:
9 0 RESUME 0
10 2 LOAD_FAST 0 (self)
4 LOAD_ATTR 0 (x)
14 RETURN_VALUE
其中順序為按照方法字母序列出,非原始定義的順序