理解 Python 的執行方式,與字節碼 bytecode 玩耍 (上)


這里有個博客講 Python 內部機制,已經有一些中文翻譯

可能因為我用的Python 3.5,例子跑起來有些不一樣。

此外,我又查了其他一些參考資料,總結如下:

 

Python 的執行方式

 

先看一個比較詳細的步驟分解:

 

>>> a = "hello"

輸入這行代碼之后,你一按回車,Python就會執行四步操作:

 

1  lexing: 詞法分析,就是把一個句子分解成 token。大致來說,就是用str.split()可以實現的功能。            

2  parsing:解析,就是把這些 token 組裝成一個邏輯結構。

3  compiling:編譯,把這個邏輯結構轉化成一個或者多個code object (代碼對象)

4  interpreting:解釋,執行每個code object 代表的代碼。

 

還有一種比較簡單的說法是這樣的:

Python 程序的執行過程就是,它先把代碼編譯成 bytecode (字節碼)指令,交給虛擬機,逐條執行 bytecode 指令。

 

這兩種說法基本上是一樣的,只是存在一個code object 和 bytecode 的差異。那么它們之間存在怎樣的關系呢?

從操作上說,bytecode 可以在 code object 的屬性中找到。

 

分清function object、code object ,以及 bytecode

 

>>> def double(a):
    return a*2

>>> double <function double at 0x000001D8082E48C8>

為什么粘貼到這里對齊會是這樣?先不管了。

從上面可以看到,定義一個函數之后,它就成了一個function object (函數對象)。只要不使用函數調用符號——也就是小括號——這個函數就不會執行。

但是它已經被編譯了,可以通過這個function object 的__code__ 屬性找到它的 code object 

 

>>> def double(a):
return a*2

>>> double
<function double at 0x00000169C5F7FF28>

>>> type (double)
<class 'function'>


>>> double.__code__  #找到double 函數對象的 code object

<code object double at 0x00000169C5F36AE0, file "<pyshell#58>", line 1>


>>> type(double.__code__)
<class 'code'>

最后一行可以看到, code object 的類型是 ‘code’ 

前面說過,bytecodecode object 的一個屬性的值。這個屬性名為 co_code

code object 的co_code屬性里面,存放了一個字符串,它就是bytecode 序列:

>>> double.__code__.co_code
b'|\x00\x00d\x01\x00\x14S'

 

bytecode 是幾個意思?

>>> double.__code__.co_code
b'|\x00\x00d\x01\x00\x14S'
>>> type(double.__code__.co_code)
<class 'bytes'>
>>> len(double.__code__.co_code)
8

它的類型是‘bytes’ ,長度是8。 你可能覺得奇怪,這個8是怎么數出來的?

注意: Python 3 中 str 類型大致相當於 Python 2 中的unicode 類型,但是 Python 3 中 bytes 類型並不是Python 2 中的 str 類型改了個名字。

bytes 是二進制序列,它的每個元素都是一個整數,值在0-255之間。

>>> for i in double.__code__.co_code:
    print (i, end="    ")

    
124    0    0    100    1    0    20    83   
>>> double.__code__.co_code[-1] 83

 

是不是正好8個元素? 第一個是124,最后一個是83

>>> chr(124)
'|'
>>> chr(83)
'S'

 

這里是一個很讓人迷惑的地方:為什么要把 '|' 、'S'這樣的字符和 x00 這樣的十六進制表示混在一起?這其實只是Python 在顯示 bytes 類型的對象給你看的時候,會把ASCII 碼范圍內的十六進制元素直接用ASCII 字符顯示出來。就像你們學校的成績光榮榜上,前20名會顯示照片,第21名之后只顯示名字了。人家的顯示方法就是這樣。

 

這段 bytecode 由8個整數組成,每個整數都有深刻的含義,不亞於昆汀的《八惡人》

可能你已經猜到,其中必定有一些代表着指令,整數是一個字典中的鍵,我們需要的是這個字典中的值,也就是指令的名字

這個字典就在文件opcode.py里。

def_op('LOAD_CONST', 100)       # Index in const list
def_op('BUILD_TUPLE', 102)      # Number of tuple items
def_op('BUILD_LIST', 103)       # Number of list items
def_op('BUILD_SET', 104)        # Number of set items

好像……這些整數才是鍵值對里的值,我們需要的是鍵。

有一個方法幫你找出值 (不要忘了先 import opcode)

>>> opcode.opmap["LOAD_FAST"]
124
>>> opcode.opmap["RETURN_VALUE"]
83

找值好像沒什么意思嘛,我們更需要的是找到鍵。

>>> opcode.opname[83]
'RETURN_VALUE'
>>> opcode.opname[124]
'LOAD_FAST'


其實找鍵也不需要,Python 有個dis反匯編工具可以用 (不要忘了先 import dis)
>>> dis.dis(double)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (2)
              6 BINARY_MULTIPLY
              7 RETURN_VALUE

再回頭看看那8個整數(這就是 bytecode的意思 —— 用for循環把 bytecode 迭代一遍得到的數字,代表一個指令序列 )


124 0 0 100 1 0 20 83
偏移量  0     1    2     3     4    5    6     7 


很明顯,第2列 的0 3 6 7 ,就是每個字節的偏移量咯


下篇在這里

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM