計划寫關於Python中如何實現屬性管理、函數(或類方法)管理、類管理的幾篇成系列的文章。
而這篇文章寫在這個系列之前,希望對后面幾篇文章的理解有所幫助。
老實說,我也是在網上搜索了一些資料才寫的這篇文章,如果有的地方寫的不夠好,請指正...
何為編譯?
- 生成目標文件。
- 且目標文件是針對特定的 CPU 體系的(注意,開頭用了"且"字)。
為ARM生成的目標文件,不能被用於MIPS的CPU,也不能用於x86的CPU。反過來說也是成立的。
也就是說這段代碼在生成目標文件的過錯中就已經被翻譯成了目標CPU指令,所以如果這個程序需要在另外一種CPU上面運行,這個代碼就必須重新翻譯。
而上述這個翻譯過程叫做編譯。
何為解釋?
對於各種非編譯型語言(例如python/java)來說,可能不存在某種翻譯成中間文件的過錯,可能存在某種編譯成中間文件的過程
如果存在翻譯過錯,那么他們翻譯生成的通常是一種『平台無關』的中間代碼,這種代碼一般不是針對特定的CPU平台,他們是在運行過程中才被翻譯成目標CPU指令的,因而在ARM CPU上能執行,換到MIPS也能執行,換到x86也能執行,不需要重新對源代碼進行翻譯。
而由於這些中間代碼並不是能在CPU上直接運行,所以需要某種中介(叫做虛擬機)在執行時負責把代碼翻譯成CPU能執行的指令。
區別
編譯型語言生成的文件已經針對某個特定的CPU生成了最終文件,所以在下載的時候針對不同的CPU下載不同的文件,這就是為什么在網上下載某個文件時,Windows系統用戶下載Windows的下載包(因為Windows是操作x86系列CPU的),而Android手機(因為Android是操作ARM系列CPU的)需要下載Android類型的下載包。
針對解釋型語言,因為你的中間文件並不是針對某個CPU的,所以如果某人在網上共享了一個Python源代碼或者中間代碼(字節碼),那么不管你運行什么操作系統,下載的文件是一樣的。而如果你原來沒有下載虛擬機的話,可能你需要下載一個虛擬機才能夠運行這個下載的文件。
你可能會說,我只要裝了Python,就可以運行下載下來的Python文件了呀,這是因為你下載的Python運行包中已經包含了一個虛擬機。
簡單總結:
1,編譯型語言在編譯過程中生成目標平台的指令,解釋型語言在運行過程中才生成目標平台的指令。
2,虛擬機的任務是在運行過程中將中間代碼翻譯成目標平台的指令。
優缺點
- 解釋語言需要考慮不同平台的共性。比如A平台支持加法和乘法,而B平台僅支持加法。為了執行9乘9這個操作同時還要保證它在A和B兩個平台上都能運行,必須將9乘9翻譯成A.B兩平台都支持的加法,即9個9相加。所以勢必會影響效率。
- 解釋語言具有跨平台的優點,只要在平台上裝了針對於該平台的虛擬機,那么一次編寫,N次運行,所以生產效率高。
- 當代碼發生更改時,解釋語言由於時解釋一句執行一句,效率不會發生更改。但是編譯語言需要重新編譯,效率會受到影響。
- 對解釋型語言來說,代碼的錯誤檢查發生在執行時。
Python的運作模式
講了這么多,下面的部分是才是真正對我們編寫代碼有實際影響的。
python運行時分為下面的四步:
- 詞法分析
- 句法分析
- 編譯
- 解釋
- 詞法分析的工作就是將輸入的原始代碼分解為一些符號token(包括標示符,關鍵字,數字, 操作符等)。這個過程中是不會報任何錯的。
- 句法分析程序再接收這些符號,並用一種結構來展現它們之間的關系(在這種情況下使用的抽象語法樹)。此時如果出現句法錯誤,會有提示。
我們常說,Python運行時不會執行函數,所以也不會報函數中的錯誤。
其實是說的片面的,請看下面的代碼,我在函數f中寫了一個語法錯誤,Python會報錯。
L=[1,2,3]
def f():
L1=[for i in L]
執行結果如下:
File "hh.py", line 4
L1=[for i in L]
^
SyntaxError: invalid syntax
- 在句法分析后Python接收這棵抽象語法樹,並將它轉化為一個(或多個)代碼對象。此時會出現我們說的中間碼,或者說字節碼。
>>> def f(x=1,y=2):
... a='a'
... b='b'
... return x+y
...
>>> f.__code__
<code object f at 0x7fe93cdfc5d0, file "<stdin>", line 1>
>>> dir(f.__code__)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
>>> f.__code__.co_name
'f'
>>> f.__code__.co_nlocals #定義的局部變量個數
4
>>> f.__code__.co_varnames
('x', 'y', 'a', 'b')
>>> f.__code__.co_argcount
2
>>> f.__code__.co_code #字節碼
b'd\x01\x00}\x02\x00d\x02\x00}\x03\x00|\x00\x00|\x01\x00\x17S'
如果我們直接運行一個Python文件,那么Python在執行完后就把這個字節碼文件刪除掉了,因為Python認為復用性不高。
但是如果是一個模塊文件,那么Python會存起來,我們來做個實驗。
a.py
import b
b.py
def f(a=1):
print(a)
當我們運行只有唯一一條語句的a.py時,Python會創立一個__pycache__的文件夾,其中就包括了b.cpython-34.pyc文件,這個就是b.py的字節碼文件,如果用open函數打開來看看,就會看到下面的二進制文件,如果用普通方式打開,那么會提示編碼錯誤。
>>> open('__pycache__/b.cpython-34.pyc','rb').read()
b'\xee\x0c\r\n0%\xd8U\x19\x00\x00\x00\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00@\x00\x00\x00s\x13\x00\x00\x00d\x00\x00d\x01\x00d\x02\x00\x84\x01\x00Z\x00\x00d\x03\x00S)\x04\xe9\x01\x00\x00\x00c\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00C\x00\x00\x00s\x0e\x00\x00\x00t\x00\x00|\x00\x00\x83\x01\x00\x01d\x00\x00S)\x01N)\x01\xda\x05print)\x01\xda\x01a\xa9\x00r\x04\x00\x00\x00\xfa\x13/home/aaa/proj/b.py\xda\x01f\x01\x00\x00\x00s\x02\x00\x00\x00\x00\x01r\x06\x00\x00\x00N)\x01r\x06\x00\x00\x00r\x04\x00\x00\x00r\x04\x00\x00\x00r\x04\x00\x00\x00r\x05\x00\x00\x00\xda\x08<module>\x01\x00\x00\x00s\x00\x00\x00\x00'
- 解釋器逐個接收這些代碼對象,並執行它們所代表的代碼。
題外話
以下題外話摘錄於網上,如果有什么地方不對,請指正。
現在關於解釋和編譯的界限也不是特別清晰了。
Java需要預先把代碼編譯成虛擬機指令的,然后在運行這些虛擬機指令,有的教科書上會成為混合型或者半編譯型。
像Python和lua這樣就更不好分了,可以直接解釋源代碼運行,也可以編譯為虛擬機指令然后再運行。
php編譯之后的結果可以被Web Server緩存起來,甚至還可以先被翻譯為C++,然后再編譯。
.NET 的CLR運行時是Windows的組成部分,編譯好的.NET 系列語言的代碼直接生成可執行文件,然后被“直接”執行,看起來跟C沒有什么太大的差別。
JavaScript可以被V8引擎編譯為機器碼然后執行,如果在node.js下,這個編譯結果被緩存起來了,你說這跟編譯好再執行的C有什么區別?
編譯型語言
1、編輯:用編輯軟件(EDIT.EXE或記事本)形成源程序(.ASM),如:LX.ASM;
2、匯編:用匯編程序(MASM.EXE)對源程序進行匯編,形成目標文件(.OBJ),格式如下:MASM LX.ASM;
3、連接:用連接程序(LINK.EXE)對目標程序進行連接,形成可執行文件(.EXE),格式如下:LINK LX.OBJ;
4、執行:如果結果在屏幕在顯示,則直接執行可執行文件。
5、調試:用調試程序(DEBUG.EXE)對可執行文件進行調試,格式如下:DEBUG LX.EXE
目標代碼由機器指令組成,一般不能獨立運行,因為源程序中可能使用了某些匯編程序不能解釋引用的庫函數,而庫函數代碼又不在源程序中,此時還需要鏈接程序完成外部引用和目標模塊調用的鏈接任務,最后輸出可執行代碼
如果對這個方向很感興趣,那么下面的幾篇文章可以進一步閱讀:
http://python.jobbole.com/81660/
http://developer.51cto.com/art/201309/410862.htm
http://developer.51cto.com/art/201003/190924.htm