Python 位元組碼是什麼

2022-05-15 02:11:21 字數 4145 閱讀 3445

如果你曾經編寫過 python,或者只是使用過 python,你或許經常會看到 python 源**檔案——它們的名字以 .py 結尾。你可能還看到過其它型別的檔案,比如以 .pyc 結尾的檔案,或許你可能聽說過它們就是 python 的 「 位元組碼(bytecode)」 檔案。(在 python 3 上這些可能不容易看到 —— 因為它們與你的 .py 檔案不在同乙個目錄下,它們在乙個叫 __pycache__ 的子目錄中)或者你也聽說過,這是節省時間的一種方法,它可以避免每次執行 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 使用三種型別的棧:

呼叫棧(call stack)。這是執行 python 程式的主要結構。它為每個當前活動的函式呼叫使用了乙個東西 —— 「 幀(frame)」,棧底是程式的入口點。每個函式呼叫推送乙個新的幀到呼叫棧,每當函式呼叫返回後,這個幀被銷毀。

在每個幀中,有乙個 計算棧(evaluation stack) (也稱為 資料棧(data stack))。這個棧就是 python 函式執行的地方,執行的 python **大多數是由推入到這個棧中的東西組成的,操作它們,然後在返回後銷毀它們。

在每個幀中,還有乙個 塊棧(block stack)。它被 python 用於去跟蹤某些型別的控制結構:迴圈、try / except 塊、以及 with 塊,全部推入到塊棧中,當你退出這些控制結構時,塊棧被銷毀。這將幫助 python 了解任意給定時刻哪個塊是活動的,比如,乙個 continue 或者 break 語句可能影響正確的塊。

大多數 python 位元組碼指令操作的是當前呼叫棧幀的計算棧,雖然,還有一些指令可以做其它的事情(比如跳轉到指定指令,或者操作塊棧)。

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

乙個 load_name 指令去查詢函式物件 my_function,然後將它推入到計算棧的頂部

另乙個 load_name 指令去查詢變數 my_variable,然後將它推入到計算棧的頂部

乙個 load_const 指令去推入乙個實整數值 2 到計算棧的頂部

乙個 call_function 指令

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

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

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

import

disdis.dis(hello)

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

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

>>> hello.__code__"

", line 1>

>>> hello.__code__

.co_consts

(none,

'hello, world!')

>>> hello.__code__

.co_varnames

()>>> hello.__code__

.co_names('

print

',)

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

許多位元組碼指令 —— 尤其是那些推入到棧中的載入值,或者在變數和屬性中的儲存值 —— 在這些元組中的索引作為它們引數。

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

load_global 0:告訴 python 通過 co_names (它是 print 函式)的索引 0 上的名字去查詢它指向的全域性物件,然後將它推入到計算棧

load_const 1:帶入 co_consts 在索引 1 上的字面值,並將它推入(索引 0 上的字面值是 none,它表示在 co_consts 中,因為 python 函式呼叫有乙個隱式的返回值 none,如果沒有顯式的返回表示式,就返回這個隱式的值 )。

call_function 1:告訴 python 去呼叫乙個函式;它需要從棧中彈出乙個位置引數,然後,新的棧頂將被函式呼叫。

現在,你已經了解的足夠多了,你可能會想 「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位元組碼簡介

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...