python編譯相關 具體編譯步驟 Python代碼的編譯和執行過程可以更詳細地描述如下: 詞法分析(Lexical Analysis)和語法分析(Syntax Analysis): Python解釋器首先會對源代碼進行詞法分析和語法分析。詞法分析器會將源代碼分解成詞法單元(tokens),這些單元 ...
python編譯相關
具體編譯步驟
Python代碼的編譯和執行過程可以更詳細地描述如下:
-
詞法分析(Lexical Analysis)和語法分析(Syntax Analysis):
Python解釋器首先會對源代碼進行詞法分析和語法分析。詞法分析器會將源代碼分解成詞法單元(tokens),這些單元是語言的基本構建塊,例如關鍵字、標識符、運算符等。語法分析器會根據語言的語法規則將這些詞法單元組織成抽象語法樹(Abstract Syntax Tree,AST),這是源代碼的一種中間表示形式。-
抽象語法樹是什麼樣的:(舉例)
抽象語法樹(Abstract Syntax Tree,AST)是在編譯過程中常用的一種數據結構,用於表示源代碼的語法結構。讓我們以一個簡單的Python代碼示例來說明:
def greet(name): return f"Hello, {name}!" print(greet("Alice"))
這段代碼的抽象語法樹可以如下所示(簡化表示):
Module( body=[ FunctionDef( name='greet', args=arguments( //arg 表示函數的一個參數,arguments 則表示函數定義中的參數集合。 args=[ arg(arg='name', annotation=None) ], defaults=[], //defaults:這表示位置參數的預設值。它也是一個列表,但在這個例子中是空的。如果函數的某些參數有預設值,那麼這裡會指定這些預設值。 kw_defaults=[], //kw_defaults:這表示關鍵字參數的預設值。與 defaults 類似,它也是一個列表。然而,在這個例子中也是空的,表示函數的關鍵字參數沒有預設值。 kwarg=None, //kwarg:這表示用於收集未明確定義的額外關鍵字參數的關鍵字參數的名稱。在這個例子中,它設置為 None,表示函數不接受超出明確定義的關鍵字參數之外的其他關鍵字參數。 kwonlyargs=[], //kwonlyargs:這表示僅關鍵字參數。關鍵字參數是一種只能通過關鍵字傳遞並且在參數列表中在 * 之後指定的參數。在這個例子中也是一個空列表。 kwargannotation=None //kwargannotation:這表示 kwarg 的註釋,如果有的話。註釋是可選的元數據,可以附加到函數參數上,指示參數的預期類型或用途。在這個例子中,它設置為 None,表示未為 kwarg 指定註釋。 ), body=[ Return( //Return 表示函數的返回語句,用於從函數中返回一個值。在這裡,Return 節點表示函數的返回部分。 value=JoinedStr( //value 屬性指定了要返回的值。在這裡,value 是一個 JoinedStr 對象,它表示一個由多個字元串拼接而成的複合字元串。JoinedStr 表示一個由多個字元串組成的複合字元串。 values=[ //values 屬性是一個列表,包含了組成複合字元串的各個部分。在這裡,複合字元串由 'Hello, '、Name 對象和 '!' 三部分組成。 Str(s='Hello, '), FormattedValue( //FormattedValue 表示格式化值,用於在字元串中插入變數。value 屬性是一個 Name 對象,表示要插入的變數名。在這裡,Name(id='name', ctx=Load()) 表示要插入的變數是函數參數 name。 value=Name(id='name', ctx=Load()), conversion=-1, format_spec=None ), Str(s='!') ] ) ) ], decorator_list=[] ), Expr( //Expr 表示一個表達式語句,用於執行函數調用或其他表達式。 value=Call( //Call 表示函數調用表達式。func 屬性是一個 Name 對象,表示要調用的函數名。在這裡,Name(id='print', ctx=Load()) 表示要調用的函數是內置函數 print()。 func=Name(id='print', ctx=Load()), args=[ Call( func=Name(id='greet', ctx=Load()), //id 和 ctx 分別是 Name 對象的兩個屬性:id 屬性:表示標識符的名稱。在這裡,id='print' 表示標識符的名稱是 'print',即函數名或變數名為 print。ctx 屬性:表示標識符的上下文(Context)。ctx 可以指定標識符在代碼中的使用方式,例如載入(load)、存儲(store)等。在這裡,ctx=Load() 表示該標識符在這個上下文中是一個載入(load)的標識符,即在代碼中引用了名為 'print' 的函數或變數。 args=[ Str(s='Alice') ], keywords=[] ) ], keywords=[] ) ) ] )
在這個抽象語法樹中,我們可以看到代碼中的函數定義、函數調用以及其他表達式的結構。每個節點都表示源代碼中的一個語法結構,例如函數定義、表達式等。這種抽象語法樹的表示形式使得編譯器可以更輕鬆地分析和處理源代碼,進而進行編譯和執行。
1. 位置參數(Positional Arguments): 位置參數是函數定義中的普通參數,它們按照在函數參數列表中的順序進行傳遞,不需要指定參數名稱。例如,在函數定義中,`def my_function(a, b):` 中的 `a` 和 `b` 就是位置參數。在調用函數時,傳遞的參數值會按照位置與位置參數一一對應。 2. 預設值(Default Values): 預設值是在函數定義中為參數提供的初始值。如果在函數調用時沒有提供對應參數的值,那麼參數將使用預設值。預設值允許函數在某些情況下以可選方式接受參數。例如,在函數定義中,`def greet(name="Guest"):`, 這裡的 `name` 參數有一個預設值 `"Guest"`。如果調用 `greet()` 時沒有傳遞參數,它將使用預設值 `"Guest"`。 3. 僅關鍵字參數(Keyword-Only Arguments): 僅關鍵字參數是只能通過關鍵字傳遞的參數,這些參數出現在函數定義中的 `*` 符號之後。這意味著在函數調用時必須使用參數名稱來傳遞這些參數值。這種類型的參數可以增加函數的靈活性和可讀性。例如,在函數定義中,`def greet(name, *, prefix="Hello"):` 中的 `prefix` 參數就是一個僅關鍵字參數。這意味著在調用 `greet()` 時必須通過關鍵字指定 `prefix` 參數的值,例如 `greet("Alice", prefix="Hi")`。 4. 註釋(Annotations): 註釋是Python中的一種元數據,用於為函數參數或變數提供類型信息或其他說明。註釋不會影響代碼的運行,但可以提供更多的信息,幫助理解代碼的含義和用法。例如,在函數定義中,`def greet(name: str):`, 這裡的 `str` 就是參數 `name` 的註釋,指示其應該是一個字元串類型。註釋可以在函數定義時使用冒號(`:`)後面的形式指定。在實際運行中,這些註釋並不會被Python解釋器使用,但可以被其他工具或IDE用來進行類型檢查或提供代碼提示。
-
-
編譯成位元組碼:
接下來,AST會被編譯成位元組碼。這個階段由Python解釋器的編譯器組件完成。位元組碼是一種中間表示形式,類似於彙編語言,它包含一系列的指令,用於執行相應的操作。位元組碼與具體的硬體和操作系統無關,也就是說生成的位元組碼可以在任何支持Python解釋器的平臺上執行,而不需要重新編譯。 -
執行位元組碼:
Python虛擬機(Python Virtual Machine,簡稱為VM)會負責執行位元組碼。虛擬機是一個解釋器,它按照位元組碼中的指令逐條執行。在執行過程中,虛擬機會將位元組碼轉換為機器代碼,併在底層硬體上執行。這個過程是動態的,允許Python代碼在運行時進行一些優化和動態調整。 -
優化和動態調整:
在執行過程中,Python解釋器還會進行一些優化和動態調整。例如,解釋器可能會對頻繁執行的代碼塊進行編譯優化,將其編譯成本地機器碼以提高執行效率。此外,解釋器還會動態地調整記憶體分配和管理策略,以最大限度地減少記憶體占用和提高性能。 -
pyc是怎麼產生的:
在首次運行Python程式時,解釋器會將源代碼編譯成位元組碼,並且會將這些位元組碼緩存到磁碟上的 .pyc 文件中(如果存在對應的 .py 文件)。這樣,在下次運行相同的程式時,解釋器可以直接載入 .pyc 文件,而無需重新編譯源代碼。這種緩存的機制可以加快程式的啟動速度,特別是對於大型項目或經常被重覆執行的腳本來說。需要註意的是,.pyc 文件並不是與特定機器或平臺相關的二進位文件。它們是跨平臺的中間位元組碼表示形式,因此可以在不同的平臺上被載入和執行。
整個過程中,Python代碼並不會直接編譯成機器代碼,而是先被解釋器解析為位元組碼,然後由解釋器執行位元組碼。這種解釋型的方式使得Python具有跨平臺性,因為位元組碼是與硬體無關的,只要有對應的解釋器,Python代碼就可以在不同平臺上執行。。
如何生成pyc文件
-
在py程式內部
使用 Python 自帶的
py_compile
模塊來將.py
文件編譯成.pyc
文件。這個模塊提供了一個名為compile
的函數,你可以使用它來編譯 Python 源文件。import py_compile # 指定要編譯的 Python 源文件路徑 source_file = 'example.py' # 編譯 Python 源文件,並生成對應的 .pyc 文件 py_compile.compile(source_file)
運行這段代碼將會在源文件所在的目錄下生成一個與源文件同名的
.pyc
文件。compileall
模塊也是用於將 Python 源文件編譯成位元組碼文件的工具,它比py_compile
模塊更加靈活,可以批量編譯目錄下的所有.py
文件。import compileall # 指定要編譯的目錄路徑 directory = '/path/to/directory' # 編譯目錄下的所有 Python 源文件,並生成對應的 .pyc 文件 compileall.compile_dir(directory)
這將會編譯指定目錄下的所有 Python 源文件,併在相同目錄下生成對應的
.pyc
文件。 -
使用命令行工具來編譯 Python 源文件,
例如在命令行中執行以下命令:
python -m py_compile example.py
這將在同一目錄下生成一個名為
example.pyc
的文件。
pyo
.pyo
文件與 .pyc
文件相似,只是在優化模式下生成,並且可能會刪除一些調試信息和註釋。
在 Python 3.x 中,預設情況下,當你使用優化標誌(-O
或 -OO
)運行 Python 解釋器時,生成的位元組碼文件將使用 .pyo
擴展名而不是 .pyc
。這些優化標誌會啟用優化編譯器標誌,從而在生成的位元組碼中刪除一些調試信息或註釋,以減小位元組碼文件的大小並提高執行效率。
O
:啟用優化編譯,將會刪除assert
語句。OO
:啟用更嚴格的優化,將會刪除assert
語句以及文檔字元串(docstrings)。
生成的 .pyo
文件可以被 Python 解釋器載入和執行,與 .pyc
文件的使用方式相同。不過需要註意的是,.pyo
文件並不會自動替換 .pyc
文件,而是作為 .pyc
文件的一個替代品。因此,如果你在優化模式下運行了一個 Python 腳本,它會生成一個 .pyo
文件,但如果以普通模式再次運行同一個腳本,它還是會生成 .pyc
文件。
拿到pyc文件後反編譯回py文件
利用uncompyle6
這個Python 反編譯工具它用於將 Python 位元組碼文件(.pyc
或 .pyo
文件)反編譯回等效的 Python 源代碼。
直接在命令行中安裝 uncompyle6
就可以用了
pip install uncompyle6
安裝完成後,就可以使用以下命令將位元組碼文件反編譯為 Python 源代碼:
uncompyle6 sample.pyc > sample.py
或者,如果是一個 .pyo
文件,也可以用相同的方式進行反編譯:
uncompyle6 sample.pyo > sample.py
解釋和編譯
編譯器:先整體編譯再執行
解釋器:邊解釋邊執行
優缺點:
編譯方式:運行速度快,但是任何的改動都要整體重新編譯。可以脫離編譯環境運行。C語言
解釋方式:運行速度慢,但是部分的改動不需要整體重新編譯。不可以脫離解釋器環境運行。python
說是python慢,但是也有優化速度的方式,也就是生成pyc文件的方式。參考了JAVA的位元組碼做法,但並不完全相同。其作用就是減少重覆的解釋工作。
Python 有幾種不同的解釋器
最常見的包括:
- CPython:CPython 是 Python 的官方解釋器,它是使用 C 語言實現的。CPython 是最常用的 Python 解釋器,它執行 Python 代碼,並提供了許多標準庫和擴展模塊。
- Jython:Jython 是一個在 Java 平臺上運行的 Python 解釋器。它將 Python 代碼編譯成 Java 位元組碼,然後在 Java 虛擬機(JVM)上執行。Jython 可以直接調用 Java 類庫,並且與 Java 代碼可以無縫集成。
- IronPython:IronPython 是一個在 .NET 平臺上運行的 Python 解釋器。它將 Python 代碼編譯成 .NET 中間語言(CIL),然後在 .NET 運行時上執行。IronPython 可以與 .NET 框架和庫進行交互,並且可以通過使用 .NET 語言擴展 Python。
- PyPy:PyPy 是一個用 Python 實現的解釋器,它旨在提供更高的性能。PyPy 使用即時(JIT)編譯技術來動態優化 Python 代碼,並且通常比 CPython 提供更好的性能。
除了這些常見的解釋器之外,還有一些其他的 Python 解釋器,例如 MicroPython(針對嵌入式系統)、Stackless Python(用於併發編程)、Pyston(用於性能優化)、Nuitka(將 Python 代碼編譯成 C 或 C++)、Shed Skin(將 Python 代碼轉換成 C++)等。