淺談Python的編譯過程與執行原理


python編譯過程和執行原理

(1)python執行原理

 這里的解釋執行是相對於編譯執行而言的。我們都知道,使用C/C++之類的編譯性語言編寫的程序,是需要從源文件轉換成計算機使用的機器語言,經過鏈接器鏈接之后形成了二進制的可執行文件。運行該程序的時候,就可以把二進制程序從硬盤載入到內存中並運行。

      但是對於Python而言,python源碼不需要編譯成二進制代碼,它可以直接從源代碼運行程序。當我們運行python文件程序的時候,python解釋器將源代碼轉換為字節碼,然后再由python解釋器來執行這些字節碼。這樣,python就不用擔心程序的編譯,庫的鏈接加載等問題了。

      對於python解釋語言,有以下3方面的特性:

  1. 每次運行都要進行轉換成字節碼,然后再有虛擬機把字節碼轉換成機器語言,最后才能在硬件上運行。與編譯性語言相比,每次多出了編譯和鏈接的過程,性能肯定會受到影響;而python並不是每次都需要轉換字節碼,解釋器在轉換之前會判斷代碼文件的修改時間是否與上一次轉換后的字節碼pyc文件的修改時間一致,若不一致才會重新轉換。
  2. 由於不用關心程序的編譯和庫的鏈接等問題,開發的工作也就更加輕松啦。
  3. python代碼與機器底層更遠了,python程序更加易於移植,基本上無需改動就能在多平台上運行。

      在具體計算機上實現一種語言,首先要確定的是表示該語言語義解釋的虛擬計算機,一個關鍵的問題是程序執行時的基本表示是實際計算機上的機器語言還是虛擬機的機器語言。這個問題決定了語言的實現。根據這個問題的回答,可以將程序設計語言划分為兩大類:編譯型語言和解釋型語言。

  1.   編譯實現的語言,如:C、C++、Fortran、Pascal、Ada。由編譯型語言編寫的源程序需要經過編譯,匯編和鏈接才能輸出目標代碼,然后由機器執行目標代碼。目標代碼是有機器指令組成,不能獨立運行,因為源程序中可能使用了一些匯編程序不能解釋引用的庫函數,而庫函數又不在源程序中,此時還需要鏈接程序完成外部引用和目標模板調用的鏈接任務,最后才能輸出可執行代碼。
  2.  解釋型語言,解釋器不產生目標機器代碼,而是產生中間代碼,這種中間代碼與機器代碼不同,中間代碼的解釋是由軟件支持的,不能直接使用在硬件上。該軟件解釋器通常會導致執行效率較低,用解釋型語言編寫的程序是由另一個可以理解中間代碼的解釋程序執行的。和編譯的程序不同的是, 解釋程序的任務是逐一將源代碼的語句解釋成可執行的機器指令,不需要將源程序翻譯成目標代碼再執行。對於解釋型語言,需要一個專門的解釋器來執行該程序,每條語句只有在執行是才能被翻譯,這種解釋型語言每執行一次就翻譯一次,因而效率低下。
  3.  Java解釋器,java很特殊,java是需要編譯的,但是沒有直接編譯成機器語言,而是編譯成字節碼,然后在Java虛擬機上用解釋的方式執行字節碼。Python也使用了類似的方式,先將python編譯成python字節碼,然后由一個專門的python字節碼解釋器負責解釋執行字節碼。
  4.   python是一門解釋語言,但是出於效率的考慮,提供了一種編譯的方法。編譯之后就得到pyc文件,存儲了字節碼。python這點和java很類似,但是java與python不同的是,python是一個解釋型的語言,所以編譯字節碼不是一個強制的操作,事實上,編譯是一個自動的過程,一般不會在意它的存在。編譯成字節碼可以節省加載模塊的時間,提高效率。
  5.   除了效率之外,字節碼的形式也增加了反向工程的難度,可以保護源代碼。這個只是一定程度上的保護,反編譯還是可以的。

(2)Python內部執行過程

一、編譯過程概述

  當我們執行Python代碼的時候,在Python解釋器用四個過程“拆解”我們的代碼,最終被CPU執行返回給用戶。

  首先當用戶鍵入代碼交給Python處理的時候會先進行詞法分析,例如用戶鍵入關鍵字或者當輸入關鍵字有誤時,都會被詞法分析所觸發,不正確的代碼將不會被執行。

  下一步Python會進行語法分析,例如當"for i in test:"中,test后面的冒號如果被寫為其他符號,代碼依舊不會被執行。

  下面進入最關鍵的過程,在執行Python前,Python會生成.pyc文件,這個文件就是字節碼,如果我們不小心修改了字節碼,Python下次重新編譯該程序時會和其上次生成的字節碼文件進行比較,如果不匹配則會將被修改過的字節碼文件進行覆蓋,以確保每次編譯后字節碼的准確性。

  那么什么是字節碼?字節碼在Python虛擬機程序里對應的是PyCodeObject對象。.pyc文件是字節碼在磁盤上的表現形式。簡單來說就是在編譯代碼的過程中,首先會將代碼中的函數、類等對象分類處理,然后生成字節碼文件。有了字節碼文件,CPU可以直接識別字節碼文件進行處理,接着Python就可執行了。

二、過程圖解

python編譯過程和執行原理

(1)python執行原理

 這里的解釋執行是相對於編譯執行而言的。我們都知道,使用C/C++之類的編譯性語言編寫的程序,是需要從源文件轉換成計算機使用的機器語言,經過鏈接器鏈接之后形成了二進制的可執行文件。運行該程序的時候,就可以把二進制程序從硬盤載入到內存中並運行。

      但是對於Python而言,python源碼不需要編譯成二進制代碼,它可以直接從源代碼運行程序。當我們運行python文件程序的時候,python解釋器將源代碼轉換為字節碼,然后再由python解釋器來執行這些字節碼。這樣,python就不用擔心程序的編譯,庫的鏈接加載等問題了。

      對於python解釋語言,有以下3方面的特性:

  1. 每次運行都要進行轉換成字節碼,然后再有虛擬機把字節碼轉換成機器語言,最后才能在硬件上運行。與編譯性語言相比,每次多出了編譯和鏈接的過程,性能肯定會受到影響;而python並不是每次都需要轉換字節碼,解釋器在轉換之前會判斷代碼文件的修改時間是否與上一次轉換后的字節碼pyc文件的修改時間一致,若不一致才會重新轉換。
  2. 由於不用關心程序的編譯和庫的鏈接等問題,開發的工作也就更加輕松啦。
  3. python代碼與機器底層更遠了,python程序更加易於移植,基本上無需改動就能在多平台上運行。

      在具體計算機上實現一種語言,首先要確定的是表示該語言語義解釋的虛擬計算機,一個關鍵的問題是程序執行時的基本表示是實際計算機上的機器語言還是虛擬機的機器語言。這個問題決定了語言的實現。根據這個問題的回答,可以將程序設計語言划分為兩大類:編譯型語言和解釋型語言。

  1.   編譯實現的語言,如:C、C++、Fortran、Pascal、Ada。由編譯型語言編寫的源程序需要經過編譯,匯編和鏈接才能輸出目標代碼,然后由機器執行目標代碼。目標代碼是有機器指令組成,不能獨立運行,因為源程序中可能使用了一些匯編程序不能解釋引用的庫函數,而庫函數又不在源程序中,此時還需要鏈接程序完成外部引用和目標模板調用的鏈接任務,最后才能輸出可執行代碼。
  2.  解釋型語言,解釋器不產生目標機器代碼,而是產生中間代碼,這種中間代碼與機器代碼不同,中間代碼的解釋是由軟件支持的,不能直接使用在硬件上。該軟件解釋器通常會導致執行效率較低,用解釋型語言編寫的程序是由另一個可以理解中間代碼的解釋程序執行的。和編譯的程序不同的是, 解釋程序的任務是逐一將源代碼的語句解釋成可執行的機器指令,不需要將源程序翻譯成目標代碼再執行。對於解釋型語言,需要一個專門的解釋器來執行該程序,每條語句只有在執行是才能被翻譯,這種解釋型語言每執行一次就翻譯一次,因而效率低下。
  3.  Java解釋器,java很特殊,java是需要編譯的,但是沒有直接編譯成機器語言,而是編譯成字節碼,然后在Java虛擬機上用解釋的方式執行字節碼。Python也使用了類似的方式,先將python編譯成python字節碼,然后由一個專門的python字節碼解釋器負責解釋執行字節碼。
  4.   python是一門解釋語言,但是出於效率的考慮,提供了一種編譯的方法。編譯之后就得到pyc文件,存儲了字節碼。python這點和java很類似,但是java與python不同的是,python是一個解釋型的語言,所以編譯字節碼不是一個強制的操作,事實上,編譯是一個自動的過程,一般不會在意它的存在。編譯成字節碼可以節省加載模塊的時間,提高效率。
  5.   除了效率之外,字節碼的形式也增加了反向工程的難度,可以保護源代碼。這個只是一定程度上的保護,反編譯還是可以的。

(2)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-編譯一個表達式 一般使用前三個參數就夠了

  使用示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

#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有哪些變量,接上節的內容分析一下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

>>> 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 0xb7273b18file "src_file", line 2>, 98None)

 

>>> 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的反編譯:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

import dis

dis.dis(co)

>>> output

 2        0 LOAD_CONST               0 (0)

          3 LOAD_CONST               1 (<code object f at 0xb7273b18file "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去執行。

  • 第一列:行號
  • 第二列:指令在代碼塊中的偏移量
  • 第三列:指令
  • 第四列:操作數
  • 第五列:操作數說明


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM