這是完整的程式碼檔案:程式碼
1.原始碼
當您編寫 Python 腳本時,它是人類可讀的文字。這個原始碼是一切的起點。
您的 Python 原始碼以 .py 檔案編寫,是人類可讀的。此程式碼定義了程式的功能,指定變數、函數、迴圈等。
2.編譯為字節碼(編譯器)
當你執行Python程式時,第一步是將原始碼編譯為字節碼。這是由 Python 解釋器完成的:
-
語法檢查:確保沒有語法錯誤。
-
編譯:將高階原始碼轉換為字節碼,這是一種較低階的、與平台無關的表示形式。該字節碼通常駐留在 __pycache__ 目錄中的 .pyc 檔案中。
-
編譯器:Python 使用解釋器,但它首先將原始程式碼編譯為稱為字節碼的較低層級形式。
-
標記化:將程式碼分解為稱為標記的小塊(如關鍵字、運算子、識別碼)。
-
解析:分析標記以確保它們遵循 Python 的語法規則。
-
控制流程圖(CFG):表示程式在執行過程中可能遍歷的所有路徑。
-
字節碼產生:將解析後的令牌轉換為字節碼,這是Python虛擬機器(PVM)的一組指令。
讓我們深入探討一下。
Python 編譯器:儘管 Python 被稱為解釋性語言,但它確實有一個編譯步驟。詳細內容如下:
標記化:
- 將程式碼分解為稱為標記的小塊(如關鍵字、運算子、識別碼)。
-
原始碼:從您編寫的程式碼開始。
-
分詞器(Lexer):這會將原始碼分解為稱為標記的較小部分,例如關鍵字(for、if)、運算子(、-)、識別碼(變數名稱)和文字(例如數字或字串) .
-
解析:分析標記以確保它們遵循 Python 的語法規則。
-
語法分析:解析器取得這些標記並根據 Python 的語法規則檢查它們。
-
解析樹:從標記建構樹結構,表示程式碼的語法結構。
-
語意分析:確保程式碼在資料類型、範圍和其他特定於上下文的規則方面有意義。
-
控制流程圖(CFG):表示程式在執行過程中可能遍歷的所有路徑。
-
控制流程圖:表示程式碼執行過程中可能採取的所有可能路徑。
-
節點和邊:每個節點代表一個基本程式碼區塊,邊代表從一個區塊到另一個區塊的控制流。
-
字節碼產生:將解析後的令牌轉換為字節碼,這是Python虛擬機器(PVM)的一組指令。
- 字節碼是原始程式碼的更緊湊、較低層級的表示形式,並針對執行進行了最佳化。它獨立於平台,這意味著它可以在任何具有相容 PVM 的系統上運行。
-
字節碼:解析後的程式碼轉換為字節碼,一種較低階的、與平台無關的表示。
-
指令集:此字節碼是Python虛擬機器(PVM)可以執行的一組指令。字節碼儲存在 __pycache__ 目錄中的 .pyc 檔案中,以加快將來的執行速度。
3.載入字節碼(字節碼)
編譯後,Python 虛擬機器載入字節碼:
-
從快取讀取:如果字節碼之前已編譯且未更改,則從快取(__pycache__)讀取。這透過跳過編譯步驟來加快執行速度。
- 字節碼已載入到記憶體中,準備執行。然後字節碼由 PVM 執行,解釋指令以執行程式的任務。
4.由 PVM (PVM) 執行
PVM 現在解釋並執行字節碼:
-
指令執行:PVM 讀取每個字節碼指令並執行它。每條指令對應一個特定的操作,例如載入值、執行算術或呼叫函數。
-
記憶體管理:管理變數和物件的記憶體分配和釋放。
Python 中的記憶體管理:
-
引用計數:Python 追蹤記憶體中物件的引用數量。當引用計數降至零時,可以回收該物件所佔用的記憶體。
-
物件分配:程式碼在運行時在記憶體中建立 Python 物件(如整數、字串、列表)。
-
垃圾收集:Python 有一個垃圾收集器,它透過釋放不再使用的記憶體(即引用計數為零的物件)來幫助管理記憶體。
-
記憶體池:Python 使用記憶體池來更有效地分配小物件。這種池化有助於減少頻繁分配和釋放小塊記憶體的開銷。
-
記憶體最佳化:Python 應用各種最佳化來最小化記憶體使用,例如:
- PVM 執行各種執行時間最佳化以提高效率,例如某些實作(如 PyPy)中的即時 (JIT) 編譯。
- 重複使用小整數和內部字串。
- 有效管理資料結構(例如元組、列表、字典)。
範例:
-
字節碼快取:PVM 快取已編譯的字節碼,以避免每次都重新編譯原始碼。這會加快後續運轉的速度。
-
常數折疊:這涉及在編譯時而不是運行時簡化常數表達式。例如,3 * 2 可能會預先計算為 6。
所以,總而言之:PVM 就像一個管弦樂團指揮,無縫地將字節碼轉換為電腦可以執行的操作。它的美妙之處在於,由於 PVM,Python 程式碼是可移植的,無需修改即可在不同平台上運行。
我們如何查看字節碼是否產生?
導入Python模組時,Python會將原始碼編譯為位元組碼並將其儲存在__pycache__目錄中。這有助於加快未來的導入速度,避免每次導入時都需要重新編譯模組。
流程如下:
-
首次導入:首次導入模組時,Python 會將 .py 檔案編譯為字節碼。
-
pycache 目錄:字節碼儲存在 __pycache__ 目錄中,名稱類似 module_name.cpython-312.pyc。 # 312 是 Python 版本。
-
後續導入:在後續導入時,Python 會檢查 __pycache__ 目錄中是否有已編譯的字節碼,如果源代碼未更改,則使用它,從而加快導入過程。
例子:
我們有 byte.py。當我們在執行 byte.py 後從 hello_world.py 導入程式碼時,我們可以看到該特定資料夾中會有一個目錄 __pycache__ ,我們可以看到 .pyc 檔案:
from hello_world import greet
greet("Byte code")
透過使用 py_compile
py_compile模組,它允許你將Python原始檔編譯成字節碼檔。這是一種加快未來運行腳本執行速度的便捷方法。
在 byte.py 中
import py_compile
py_compile.compile('hello_world.py')
- py_compile 模組將 hello_world.py 編譯為字節碼。
- 產生的字節碼儲存在 pycache 目錄中,建立一個名為 hello_world.cpython-38.pyc (或類似文件,取決於您的 Python 版本)的檔案。
產生字節碼:
- 執行整個腳本來產生字節碼。這意味著任何頂級程式碼(例如 print("Hello, World!") 和 print("c"))都會在編譯過程中執行。
產生的字節碼:
- 字節碼包含所有函數、類別和可執行語句,Python 使用它們來加速未來腳本的導入。
顯示模組
Python 中的 dis 模組用於將字節碼反組譯成更易讀的形式。這可以幫助您了解 Python 程式碼在幕後的用途。它對於調試或了解 Python 的內部結構特別有用。
from hello_world import greet
greet("Byte code")
輸出
import py_compile
py_compile.compile('hello_world.py')
- 程式首先導入 dis 模組,這是一個用於分析 CPython 字節碼的強大工具。 CPython 是 Python 的預設實現,字節碼是 Python 解釋器的中間語言。
- 接下來,我定義了一個名為greet的簡單函數。此函數接受參數名稱並列印出問候語。儘管函數本身非常簡單,但 Python 底層發生的事情比表面上看起來更複雜。
- disassemble_function 函數使用 dis.dis() 來反組譯greet函數。 dis.dis() 將 Python 函數轉換為 Python 虛擬機器實際執行的低階位元組碼。這個字節碼是Python對greet函數的解釋,更接近機器碼。
- 當腳本呼叫 disassemble_function() 時,控制台輸出顯示我們的greet 函數的字節碼。
這是字節碼告訴我們的內容:
- LOAD_GLOBAL(0):此操作碼用於載入全域變量,在本例中是列印函數。
- LOAD_CONST(1):這會將常數值「Hello,」載入到堆疊上。
- LOAD_FAST(0):此操作碼將局部變數名稱載入到堆疊上。
- FORMAT_VALUE(0):這會格式化我們的名稱字串,準備將其插入即將建置的字串中。
- BUILD_STRING(2):這採用堆疊上的前兩個值(「Hello,」和名稱)並建立最終字串。
- CALL_FUNCTION(1):這一行呼叫函數(我們載入到堆疊上的全域列印函數),參數計數位於括號中(我們有一個參數,即我們的格式化字串)。
- POP_TOP:這會刪除堆疊頂部(上一次呼叫的結果,因為 print 傳回 None)。
- LOAD_CONST(0):不載入。
- RETURN_VALUE:這是greet函數的回傳值,由於沒有明確的return語句,因此為None。
- 本質上,字節碼顯示了Python執行我們的greet函數時執行的各個操作。理解這些指令對於開發人員理解 Python 如何執行程式碼、最佳化函數和管理資源至關重要 - 當我們運行 Python 程式碼時,所有這些都在幕後無縫地發生。
這不是一次令人愉快地深入 Python 機房的經歷嗎?繼續編碼並繼續探索該語言引擎室的深度? !
以上是Python 的內部運作原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!