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


上次寫到,Python 的執行方式是把代碼編譯成bytecode(字節碼)指令,然后由虛擬機來執行這些 bytecode

而 bytecode 長成這個樣子:  b'|\x00\x00d\x01\x00\x14S' 。顯然這個樣子適合機器看,不適合人類看。 

雖然你可以通過查字典的方式,手動把這段 bytecode 編寫成人類可以看得懂的樣子,

但是這么勞累的事情,為什么要自己親手來做呢,讓你的男仆機器來做不就好了嗎。

 

Python 的反匯編工具 dis 就可以辦到這件事。下面用絢麗的紫色來對dis.dis 的輸出結果進行分列解釋。

>>> def double(a):
    return a*2  # 並不知道為什么貼在這里縮進會是這樣

>>> import dis
>>> dis.dis(double)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (2)
              6 BINARY_MULTIPLY
              7 RETURN_VALUE

上一篇文章的末尾已經解釋過了,第2列的數字 0 3 6 7 是 bytecode 的偏移量。

第3列很好理解,都是opcode。注意這些 opcode 是給弱弱的人類看的,不是給機器看的,機器只要看b'|\x00\x00d\x01\x00\x14S' 這種東西就行了),

比如第一個opcode, 大名叫做叫LOAD_FAST,查一下資料,發現它的意思是 Pushes a reference to the local co_varnames[var_num] onto the stack.

中文意思:把本地某個東西的引用壓到棧里。

什么東西呢?那就是 co_varnames[var_num] 啦。

(內心OS: 看這形象似乎是列表或者字典,字符串不太可能,自定義對象更不可能……)

 

>>> double.__code__.co_varnames[0] # 第4列里的下標 0
'a' 
>>> double.__code__.co_consts[1] # 第4列里的下標 1
2

上次講過了:double 是函數對象 function object

double.__code__是這個函數對象的代碼對象 code object

看看返回值,a 和2, 也就是第5列

那么第1列的2是什么?看起來好像很神秘的樣子,其實不過是源代碼中的行號。本例中表示是在double 代碼的第2行。

 

上次詳細解釋過,b'|\x00\x00d\x01\x00\x14S' 其實是8個整數

 

 

>>> double.__code__.co_code
b'|\x00\x00d\x01\x00\x14S'
>>> for i in  double.__code__.co_code:
    print (i, end="    ")

    
124    0    0    100    1    0    20    83    

 

 

 

通過查字典或者另一個更加巧妙正常的辦法,你可以找出124代表的opcode是 LOAD_FAST,  100代表 LOAD_CONST

這類opcode 后面各帶兩個字節的參數,分別是0  0   和 1  0 

但有些opcode后面是沒有參數的,比如 83 代表的 RETURN_VALUE

        不了解的人可能會覺得有點奇怪,為什么RETURN_VALUE不帶參數呢,不帶參數怎么返回結果呢?

        查一查資料,RETURN_VALUE的意思是 Returns with TOS to the caller of the function. 即把TOS返回給這個函數的調用者。TOS= top of stack, 。咱出棧了。

 

現在問題來了:為什么要與字節碼 bytecode 玩耍? 直接寫Python代碼方便多了,為什么要去寫字節碼?

 

因為可以節省編譯時間,這里有一篇非常詳細的文章,作者在遺傳編程領域工作,發現他們Python 程序的總運算時間中,有50%都被編譯過程吃掉。於是作者深入到 bytecode 層次進行了小小改動,大幅削減了編譯時間,把總的運算時間降至不足原先的一半。

可惜沒有找到這篇文章的中文翻譯。不知道有沒有人肯出錢讓我翻譯。

文中寫道:

       bytecode 寫好之后,我們必須讓Python 明白它要執行這些bytecode。這時就需要創建一個完整的 code object。 

       bytecode 是 code object 的主要成分,但是還需要其他東西才能構成完整的 code object。就像雞丁是宮保雞丁中的主要食材,但它也不能沒有花生。

       這個過程中要用到types.CodeType()

       有了code object 之后,接下來可以調用Types.FunctionType,利用這些代碼創建一個函數對象( function object)。

 

上一篇寫了怎么抽絲剝繭,順着function object 找 code object,再找 bytecode,這里就完全倒過來,添枝加葉,逆流而上了。

 


免責聲明!

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



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