一、編譯過程概述
當我們執行Python代碼的時候,在Python解釋器用四個過程“拆解”我們的代碼,最終被CPU執行返回給用戶。
首先當用戶鍵入代碼交給Python處理的時候會先進行詞法分析,例如用戶鍵入關鍵字或者當輸入關鍵字有誤時,都會被詞法分析所觸發,不正確的代碼將不會被執行。
下一步Python會進行語法分析,例如當"for i in test:"中,test后面的冒號如果被寫為其他符號,代碼依舊不會被執行。
下面進入最關鍵的過程,在執行Python前,Python會生成.pyc文件,這個文件就是字節碼,如果我們不小心修改了字節碼,Python下次重新編譯該程序時會和其上次生成的字節碼文件進行比較,如果不匹配則會將被修改過的字節碼文件進行覆蓋,以確保每次編譯后字節碼的准確性。
那么什么是字節碼?字節碼在Python虛擬機程序里對應的是PyCodeObject對象。.pyc文件是字節碼在磁盤上的表現形式。簡單來說就是在編譯代碼的過程中,首先會將代碼中的函數、類等對象分類處理,然后生成字節碼文件。有了字節碼文件,CPU可以直接識別字節碼文件進行處理,接着Python就可執行了。
二、過程圖解

三、編譯字節碼
Python中有一個內置函數compile(),可以將源文件編譯成codeobject,首先看這個函數的說明:
compile(...) compile(source, filename, mode[, flags[, dont_inherit]]) -> code object
參數1:源文件的內容字符串
參數2:源文件名稱
參數3:exec-編譯module,single-編譯一個聲明,eval-編譯一個表達式 一般使用前三個參數就夠了
使用示例:
#src_file.py
#some function
def f(d=0):
c=1
print "hello"
a=9
b=8
f()
>>> a=open('src_file.py','r').read() #命令行模式中打開源文件進行編譯
>>> co=compile(a,'src_file','exec')
>>> type(co)
<type 'code'> #編譯出了codeobject對象
四、codeobject對象的屬性
codeobject有哪些變量,接上節的內容分析一下:
>>> print co.co_names #所有的符號名稱
('f', 'a', 'b')
>>> print co.co_name #模塊名、函數名、類名
<module>
>>> print co.co_consts #常量集合、函數f和兩個int常量a,b,d
(0, <code object f at 0xb7273b18, file "src_file", line 2>, 9, 8, None)
>>> print co.co_consts[1].co_varnames #可以看到f函數也是一個codeobject,打印f中的局部變量
('c',)
>>> print co.co_code #字節碼指令
dZdZdZedS
>>> print co.co_consts[1].co_firstlineno #代碼塊在文件中的起始行號
2
>>> print co.co_stacksize #代碼棧大小
2
>>> print co.co_filename #文件名
src_file #模塊名、函數名、類名
codeobject的co_code代表了字節碼,這個字節碼有什么含義?我們可以使用dis模塊進行python的反編譯:
import dis
dis.dis(co)
>>> output
2 0 LOAD_CONST 0 (0)
3 LOAD_CONST 1 (<code object f at 0xb7273b18, file "src_file", line 2>)
6 MAKE_FUNCTION 1
9 STORE_NAME 0 (f)
5 12 LOAD_CONST 2 (9)
15 STORE_NAME 1 (a)
6 18 LOAD_CONST 3 (8)
21 STORE_NAME 2 (b)
7 24 LOAD_NAME 0 (f)
27 CALL_FUNCTION 0
30 POP_TOP
31 LOAD_CONST 4 (None)
34 RETURN_VALUE
從反編譯的結果來看,python字節碼其實是模仿的x86的匯編,將代碼編譯成一條一條的指令交給一個虛擬的cpu去執行。
- 第一列:行號
- 第二列:指令在代碼塊中的偏移量
- 第三列:指令
- 第四列:操作數
- 第五列:操作數說明
