在學習 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欄的 01 : 表示目前位於程式原始碼的第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

其中順序為按照方法字母序列出,非原始定義的順序

參考資料