---
# System prepended metadata

title: Python 執行原理 Byte Code

---

# Python 執行原理 Byte Code

電腦不能夠識別高級語言，所以需要一個翻譯機把高級語言轉變成電腦能識別的機器語言，這個過程分成編譯型語言與解釋型語言。

* 編譯型語言在執行程序前，會先通過一個編譯的過程，編釋器(compiler)把程序轉變成機器語言，運行時就不需翻譯就可以執行，如C語言。
* 直譯型(解釋型)語言就是不透過編譯的過程，而是在程序運行時，通過解釋器(intepreter)對程序逐行翻譯，再直接運行，如 Ruby 跟 Python。

### *.py 執行過程
以 Python3 為例，執行一個python3 file.py 檔，此時發生：

1. Interpreter 會將 Python Source Code file.py interpret 成 Byte Code(儲存 Byte 型態的資料)。
2. Byte Code 會再被 Virtual Machine compile 成 Machine Code 以及 Executable Code 等。
3. 最後Virtual Machine 會觸發 CPU 以及系統調度來執行這份程式碼要處理的事。

**示意圖:**
![](https://i.imgur.com/mBFtPuk.png)

### Byte Code
為什麼設計這樣的流程產生一個中間程式碼(Byte Code)，而不直接生成Excutable File?

提供更靈活架構方便轉換、提昇嫁接性，如 Byte Code 可以對接 Java 的 Virtual Machine，達成跨平台、系統的效果。

可作到「一行一行執行」的概念，不花費許多資源做通到底的編譯，提昇開發效率。
常見支援 Python 的 Interpreters :
CPython （C 寫成）： 最多人使用
PyPy (RPython 寫成)
Jython (Java 寫成)

### *.pyc 用途
Byte Code 除了被 C Virtual Machine 讀取並執行外，Python 會把常使用的 Byte Code 寫進 Disk 儲存起來，變成 *.pyc 檔。通常在__pycache__資料夾下，import的library會在LIB

pyc是一種二進位制檔案，是由py檔經過直譯後生成的檔案，是 Byte Code 在 Python 裡的一個實現。pyc的內容跟python版本相關，不同版本直譯後的pyc檔案不同。

為什麼需要 .pyc 檔？
主要的理由是加速運行，py檔案變成pyc檔案後，提高載入的速度。而且pyc是一種跨平臺的位元組碼，由python的虛擬機器來執行。

*.pyc 參與Python執行過程：
* 當 Python 程序運行時，解釋的結果會暫時保存在記憶體中的 PyCodeObject中，當 Python 程序結束時，Python解釋器會把PyCodeObject寫入到pyc的檔案裡。
* 當 Python 程序第二次運行時，首先會在硬碟中尋找 .pyc檔，如果有找到，並且發現Python的原始碼被異動，就會檢查 *.py文件的更新時間與 *.pyc 文件的更新時間，如果 *.py的時間比較新，代表檔案有更新，則重複解釋器的過程；反之，就直接載入。

### *py轉成 *.pyc
py_compile 模組把py檔案編譯為pyc檔案

#### 生成單個*.pyc
把某py檔案編譯為pyc檔案。
```python=
import py_compile
py_compile.compile(r'hi.py')
print('done')
```
函式compile(file, cfile, dfile, doraise)說明：

* file：需要編譯的py檔案的路徑。
* cfile：表示編譯後的pyc檔名和路徑，預設為直接在file檔名後加c或o，o表示優化的位元組碼。
* dfile：把在錯誤資訊中顯示的file名稱用dfile名稱替換。
* doraise：True或False，如果為True，如果編譯檔案出錯會引發一個PyCompileError；否則預設顯示在sys.stderr中，而不會引發異常。
* optimize：用於編譯的最佳化層級，有效值為-1,0,1,2。-1表示當前解譯器的最佳化層級。


#### 批量生成*.pyc

把某目錄及其子目錄下的py檔案編譯為pyc檔案。
用命令列編譯一個目錄下的檔案，如：python -m compileall /root/src/
```python=
import compileall
compileall.compile_dir(r'D:\game')
print('done')
```
函式compile_dir(dir, maxlevels, ddir, force, rx, quiet)說明：

* dir：需要編譯的資料夾位置。
* maxlevels：需要遞迴編譯的子目錄的層數，預設是10層。
* ddir：把在錯誤資訊中顯示的file用ddir替換。
* force：如果為True，即使現在的pyc檔案是最新的，也會再強制編譯一次，pyc檔案中包含時間戳，python編譯器會根據時間來決定，是否需要重新生成一次pyc檔案。
* rx：為正則表示式，可過濾符合條件的目錄進行編譯。
* quiet：如果為True，則編譯後不會在標準輸出中印出資訊。

修改python原始碼中的opcode檔案(Python39\include\opcode.h & Python39\Lib\opcode.py)，新生成的pyc檔，將無法被其他版本python所使用，可防止被反編譯破解。

如何使用修改後的opcode?
更改opcode的相關檔案，再安裝python，刪除舊有的pyc檔，便可使用不同opcode的python環境。

**Reference:**
https://www.796t.com/content/1508990186.html
https://saucer-man.com/information_security/825.html
https://medium.com/citycoddee/python%E9%80%B2%E9%9A%8E%E6%8A%80%E5%B7%A7-5-python-%E5%88%B0%E5%BA%95%E6%80%8E%E9%BA%BC%E8%A2%AB%E5%9F%B7%E8%A1%8C-%E7%9B%B4%E8%AD%AF-%E7%B7%A8%E8%AD%AF-%E5%AD%97%E7%AF%80%E7%A2%BC-%E8%99%9B%E6%93%AC%E6%A9%9F%E7%9C%8B%E4%B8%8D%E6%87%82-553182101653
