python位元組碼
如果您曾經編寫過python,或者甚至只是使用過python,那麼您可能已經習慣了檢視python源**檔案。 它們的名稱以.py
結尾。 而且您可能還看到了另一種型別的檔案,其名稱以.pyc
結尾,並且您可能已經聽說它們是python的「位元組碼」檔案。 (這些在python 3上很難看到-而不是與.py
檔案位於同一目錄中,而是進入乙個名為__pycache__
的子目錄中。)也許您已經聽說這節省了一些時間這樣可以避免python每次執行時都必須重新解析源**。
但是除了「哦,那是python位元組碼」之外,您真的知道這些檔案中的內容以及python如何使用它們嗎?
python通常被描述為一種解釋性語言-一種在程式執行時將您的源**翻譯為本機cpu指令的語言-但這僅部分正確。 像許多解釋語言一樣,python實際上將源**編譯為虛擬機器的一組指令,而python直譯器是該虛擬機器的實現。 這種中間格式稱為「位元組碼」。
因此,python留下的那些.pyc
檔案不僅僅是源**的某些「更快」或「優化」版本; 它們是將在程式執行時由python的虛擬機器執行的位元組碼指令。
讓我們來看乙個例子。 這是經典的「你好,世界!」 用python編寫:
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使用三種型別的堆疊:
呼叫堆疊。 這是正在執行的python程式的主要結構。 對於每個當前活動的函式呼叫,它都有乙個專案(「框架」),堆疊的底部是程式的入口點。 每個函式呼叫都會將乙個新的框架推送到呼叫堆疊上,並且每次函式呼叫返回時,都會彈出其框架。
在每一幀中,都有乙個評估堆疊(也稱為資料堆疊)。 該堆疊是執行python函式的地方,執行python**主要包括將事物壓入該堆疊,對其進行處理以及將其彈出。
同樣在每一幀中,都有乙個塊堆疊。 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來查詢由該名稱引用全域性物件在索引0co_names
(這是print
功能),並將其推入計算堆疊
load_const 1
:取字面值在指數1co_consts
和推動它(索引0處的值是文字none
,這是目前在co_consts
因為python函式呼叫有乙個隱含的返回值none
,如果沒有明確的return
達到語句)
call_function 1
:告訴python呼叫函式; 它需要從堆疊中彈出乙個位置引數,然後新的堆疊頂部將成為要呼叫的函式。
「原始」位元組碼(作為人類不可讀的位元組)也可以在**物件上作為屬性co_code
。 如果您想嘗試手動反彙編函式,則可以使用列表dis.opname
從十進位制位元組值中查詢位元組碼指令的名稱。
其次,了解位元組碼是回答有關python問題的有用方法。 例如,我經常看到新的python程式設計師想知道為什麼某些構造比其他構造更快(例如為什麼{}
比dict()
更快)。 知道如何訪問和讀取python位元組碼可以讓您算出答案(嘗試:dis.dis("{}")
與dis.dis("dict()")
)。
最後,了解位元組碼以及python如何執行它可以為python程式設計師不常參與的一種特殊程式設計提供有用的視角:面向堆疊的程式設計。 如果您曾經使用過像forth或factor這樣的面向堆疊的語言,這可能是個老新聞,但是如果您不熟悉這種方法,那麼學習python位元組碼並了解其面向堆疊的程式設計模型是如何工作的,就很簡單了。擴充套件您的程式設計知識的方法。
由allison kaptur 用python編寫的python直譯器,是乙個在python本身(還有其他方面)中構建python位元組碼直譯器的教程,它實現了執行python位元組碼的所有機制。
最後,cpython直譯器是開源的,您可以在github上閱讀它 。 位元組碼直譯器的實現在檔案python/ceval.c
。 這是python 3.6.4版本的檔案 ; 位元組碼指令由從1266行開始的switch
語句處理。
翻譯自:python位元組碼
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...
Python位元組碼介紹
如果你曾經編寫過 python,或者只是使用過 python,你或許經常會看到 python 源 檔案 它們的名字以 py 結尾。你可能還看到過其它型別的檔案,比如以 pyc 結尾的檔案,或許你可能聽說過它們就是 python 的 位元組碼bytecode 檔案。在 python 3 上這些可能不容...