Python位元組碼介紹

2021-09-26 15:42:27 字數 3762 閱讀 8257

如果你曾經編寫過 python,或者只是使用過 python,你或許經常會看到 python 源**檔案——它們的名字以 .py 結尾。

你可能還看到過其它型別的檔案,比如以 .pyc 結尾的檔案,或許你可能聽說過它們就是 python 的 「位元組碼bytecode」 檔案。(在 python 3 上這些可能不容易看到 —— 因為它們與你的 .py 檔案不在同乙個目錄下,它們在乙個叫pycache的子目錄中)

或者你也聽說過,這是節省時間的一種方法,它可以避免每次執行 python 時去重新解析源**。

但是,除了 「噢,原來這就是 python 位元組碼」 之外,你還知道這些檔案能做什麼嗎?以及 python 是如何使用它們的?

如果你不知道,那你走運了!今天我將帶你了解 python 的位元組碼是什麼,python 如何使用它去執行你的**,以及知道它是如何幫助你的。

python 經常被介紹為它是乙個解釋型語言 —— 其中乙個原因是在程式執行時,你的源**被轉換成 cpu 的原生指令 —— 但這樣的看法只是部分正確。python 與大多數解釋型語言一樣,確實是將源**編譯為一組虛擬機器指令,並且 python 直譯器是針對相應的虛擬機器實現的。這種中間格式被稱為 「位元組碼」。

因此,這些 .pyc 檔案是 python 悄悄留下的,是為了讓它們執行的 「更快」,或者是針對你的源**的 「優化」 版本;它們是你的程式在 python 虛擬機器上執行的位元組碼指令。

我們來看乙個示例。這裡是用 python 寫的經典程式 「hello, world!」:

def

hello()

print

("hello, world!"

)

下面是轉換後的位元組碼**換為人類可讀的格式):

2

0 load_global 0

(print

)2 load_const 1

('hello, world!'

)4 call_function 1

如果你輸入那個 hello() 函式,然後使用 cpython 直譯器去執行它,那麼上述列出的內容就是 python 所執行的。它看起來可能有點奇怪,因此,我們來深入了解一下它都做了些什麼。

cpython 使用乙個基於棧的虛擬機器。也就是說,它完全面向棧資料結構的(你可以 「推入」 乙個東西到棧 「頂」,或者,從棧 「頂」 上 「彈出」 乙個東西來)。

cpython 使用三種型別的棧:

為了更好地理解,假設我們有一些呼叫函式的**,比如這個:my_function(my_variable, 2)。python 將轉換為一系列位元組碼指令:

這個 call_function 指令將有 2 個引數,它表示那個 python 需要從棧頂彈出兩個位置引數;然後函式將在它上面進行呼叫,並且它也同時被彈出(對於函式涉及的關鍵字引數,它使用另乙個不同的指令 —— call_function_kw,但使用的操作原則類似,以及第三個指令 —— call_function_ex,它適用於函式呼叫涉及到引數使用 * 或 ** 操作符的情況)。一旦 python 擁有了這些之後,它將在呼叫棧上分配乙個新幀,填充到函式呼叫的本地變數上,然後,執行那個幀內的 my_function 位元組碼。執行完成後,這個幀將被呼叫棧銷毀,而在最初的幀內,my_function 的返回值將被推入到計算棧的頂部。

如果你想玩轉位元組碼,那麼,python 標準庫中的 dis 模組將對你有非常大的幫助;dis 模組為 python 位元組碼提供了乙個 「反彙編」,它可以讓你更容易地得到乙個人類可讀的版本,以及查詢各種位元組碼指令。dis 模組的文件 可以讓你遍歷它的內容,並且提供乙個位元組碼指令能夠做什麼和有什麼樣的引數的完整清單。

例如,獲取上面的hello()函式的列表,可以在乙個 python 解析器中輸入如下內容,然後執行它:

import dis

dis.dis(hello)

函式 dis.dis() 將反彙編乙個函式、方法、類、模組、編譯過的 python **物件、或者字串包含的源**,以及顯示出乙個人類可讀的版本。dis 模組中另乙個方便的功能是 distb()。你可以給它傳遞乙個 python 追溯物件,或者在發生預期外情況時呼叫它,然後它將在發生預期外情況時反彙編呼叫棧上最頂端的函式,並顯示它的位元組碼,以及插入乙個指向到引發意外情況的指令的指標。

它也可以用於檢視 python 為每個函式構建的編譯後的**物件,因為執行乙個函式將會用到這些**物件的屬性。這裡有乙個檢視hello()函式的示例:

>>

> hello.__code__

,file

"", line 1

>

>>

> hello.__code__.co_consts

(none

,'hello, world!'

)>>

> hello.__code__.co_varnames()

>>

> hello.__code__.co_names

('print'

,)

**物件在函式中可以以屬性code來訪問,並且攜帶了一些重要的屬性:

因此,現在我們能夠理解hello()函式中所列出的位元組碼:

現在,你已經了解的足夠多了,你可能會想 「ok,我認為它很酷,但是知道這些有什麼實際價值呢?」由於對它很好奇,我們去了解它,但是除了好奇之外,python 位元組碼在幾個方面還是非常有用的。

首先,理解 python 的執行模型可以幫你更好地理解你的**。人們都開玩笑說,c 是一種 「可移植彙編器」,你可以很好地猜測出一段 c **轉換成什麼樣的機器指令。理解 python 位元組碼之後,你在使用 python 時也具備同樣的能力 —— 如果你能預料到你的 python 源**將被轉換成什麼樣的位元組碼,那麼你可以知道如何更好地寫和優化 python 源**。

第二,理解位元組碼可以幫你更好地回答有關 python 的問題。比如,我經常看到一些 python 新手困惑為什麼某些結構比其它結構執行的更快(比如,為什麼 {} 比 dict() 快)。知道如何去訪問和閱讀 python 位元組碼將讓你很容易回答這樣的問題(嘗試對比一下: dis.dis("{}") 與 dis.dis(「dict()」) 就會明白)。

最後,理解位元組碼和 python 如何執行它,為 python 程式設計師不經常使用的一種特定的程式設計方式提供了有用的視角:面向棧的程式設計。如果你以前從來沒有使用過像 forth 或 fator 這樣的面向棧的程式語言,它們可能有些古老,但是,如果你不熟悉這種方法,學習有關 python 位元組碼的知識,以及理解面向棧的程式設計模型是如何工作的,將有助你開拓你的程式設計視野。

乙個用 python 編寫的 python 解析器,它是由 allison kaptur 寫的乙個教程,它是用 python 構建的 python 位元組碼解析器,並且它實現了執行 python 位元組碼的全部構件。

最後,cpython 解析器是乙個開源軟體,你可以在 github 上閱讀它。它在檔案 python/ceval.c 中實現了位元組碼解析器。這是 python 3.6.4 發行版中那個檔案的鏈結;位元組碼指令是由第 1266 行開始的 switch 語句來處理的。

**:英文原文:

python位元組碼 Python位元組碼簡介

python位元組碼 如果您曾經編寫過python,或者甚至只是使用過python,那麼您可能已經習慣了檢視python源 檔案。它們的名稱以.py結尾。而且您可能還看到了另一種型別的檔案,其名稱以.pyc結尾,並且您可能已經聽說它們是python的 位元組碼 檔案。這些在python 3上很難看到...

python 位元組碼

python位元組碼 hello.py usr bin env python coding utf 8 import m 呼叫m裡的方法 執行之後會生成乙個m.pyc檔案 如果將m.py檔案刪除,只留hello.py和m.pyc檔案,同樣能執行出效果 對於hello.py m.py m.pyc 三個...

python 位元組碼 優化 位元組碼優化

python是一種動態語言。這意味著您在編寫 方面有很大的自由度。由於python公開了大量的自省 順便說一句,這非常有用 許多優化根本無法執行。例如,在第乙個示例中,python無法知道呼叫它時list是什麼資料型別。我可以建立乙個非常奇怪的類 class crazylist object pri...