python編譯過程和執行原理
一、編譯過程概述
當我們執行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-編譯一個表示式 一般使用前三個引數就夠了
使用示例:
12345678910111213 | #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有哪些變數,接上節的內容分析一下:
1234567891011121314151617181920212223 | >>> 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的反編譯:
123456789101112131415161718 | 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
|