一:解釋性和編譯型
梳理
編譯型:源代碼經過編譯直接變為二進制的機器語言,每次都可以直接重新運行不需要翻譯。典型的就是c語言。
解釋性:java和python都是解釋型,源代碼經過編譯變為字節碼文件,然后將字節碼放在VM上運行,達到跨平台的目的。
java和python都是邊解釋邊執行,但是解釋之前都先進行了編譯工作,編譯為vm能看懂的字節碼,vm解釋的是字節碼,非源碼和機器語言。
但是為什么python比java慢了一個級別?
主要原因是python是動態語言,java是靜態語言。
靜態語言:變量聲明的時候要聲明變量類型,這樣編譯器在程序運行時知道變量的類型。
動態語言:在程序運行時解釋器只知道變量是一個對象,至於具體是什么類型解釋器並不知道,所以每次程序運行的時候都要判斷變量的類型,才能調用方法,以此來判斷此變量對象有沒有此方法。
還有像容器類型,java中的數組變量類型必須一致;但是python中list用的多,變量類型任意。
java對於相同類型,編譯像預演一樣,在現場直播時安排連續的一塊小內存存放,尋址速度快
python則是全內存尋找變量,相比java肯定慢了。
正是因為動態語言多了一個類型判斷的過程,因此python比java慢了一個級別。
動態語言是簡單,但是解釋器在背后做的事情比靜態語言的解釋器在背后做了更多的事情。
python兩個概念,PyCodeObject和pyc文件
在硬盤上看到的pyc自然不必多說,而其實PyCodeObject則是Python編譯器真正編譯成的結果。
當python程序運行時,編譯的結果保存在位於內存中的PyCodeObject中,當Python程序運行結束時,Python解釋器則將PyCodeObject寫回到pyc文件中,前提有寫權限。
當python程序第二次運行時,首先程序會在硬盤中尋找pyc文件,如果找到對比修改時間,改動了就重新編譯,沒改動則直接載入,否則就重復上面的過程。
所以我們應該這樣來定位PyCodeObject和pyc文件,我們說pyc文件其實是PyCodeObject的一種持久化保存方式。
test.py如下
def test():
print("Hello World")
if __name=="__main__":
test()
此時python test.py發現硬盤中根本沒有pyc文件
但是如果把函數定義放到另一個a.py文件中,在test.py中from a import test
此時再python test.py就會發現有了pyc文件。這說明了什么?
說明了pyc文件的目的是為了重用。
python解釋器認為只有import的模塊,才是要被重用的模塊。對於test.py文件來講,解釋器不認為他是需要被重用的模塊,因為他會被經常的改動,把它持久化是畫蛇添足的,因為每次都要持久化為pyc文件,因此python只會把可重用的模塊持久化為pyc文件。
速度
字節碼並不能加快程序的運行速度,只是加快了代碼的加載速度。
源代碼都會被編譯為字節碼,java和Python都有這一步,當python運行主文件的時候,會將用到的其他模塊全部編譯為二進制的pyc文件來加快第二次運行程序時加載模塊的速度,省去了源代碼轉為字節碼的過程,解釋器直接在pvm中拿到pyc文件直接執行。
當創建pyc文件的時候會和模塊文件的最后一次修改時間進行映射,一旦第二次運行時會比較修改時間,如果修改時間沒變就直接拿pyc文件執行,如果改變了就重新編譯。
如果沒有創建文件的權限,那么pyc文件是在內存存在的,每次運行pyc文件都是在內存重新編譯生成,無法加快程序加載速度。
編譯
java的編譯就像預演一樣,會把你所有的錯誤找出來,例如結果是none,然后你調用了一個方法,編譯絕對無法通過。
python是沒有預演,只有現場直播,根據表演者(程序員)的水平高低,經驗少的就會在直播時出很多錯誤,經驗豐富就會犯的錯誤少,同java一樣對none對象調用方法,只有運行起來才會發現錯誤。
設計初衷
java的設計嚴謹,我認為有些過度設計,就像此次疫情中誕生的一個概念“去過度化”(2020年疫情期間修改)。
python崇尚鴨子類型和魔法方法(還有namespace),讓python天生就是多態,不用像java刻意去多態,python內置的協議就是魔法方法,不同的魔法方法分別屬於不同的大類
不再有接口的概念,盡管python用abc模塊模仿了抽象類,但我幾乎沒怎么用過。
魔法方法就像是彩蛋一樣,有各種被動的觸發方式,任何一個類都可以將魔法方法放到里面來增強類的表達
python一切皆對象:元類type繼承object類,元類type實例化了object類對象,聽上去和現實是矛盾的,好像是一個閉環,但是在代碼中並不矛盾,也是可以實現的。
矛盾是因為和現實世界映射對比起來矛盾:type繼承了父類object,然后type又自己實例化了自己,然后又實例化了老爹,這什么玩意?
繼承爹,又實例化爹在啟動階段並不矛盾,繼承了靜態的代碼又沒有調用爹的方法,開個后門讓他過去
type類沒用爹的方法,但是其他object類可能會使用object的方法,那沒問題,只要其他對象在obejct實例化之后創建就可以了。
type繼承object,object是對象,type也是對象;而type創建了所有的對象,那么一切都是對象。
type對象都是type自己創建的,用了一個指針而已。
python一切皆對象,1在java是基本數據類型,存放棧中,而pyhton一切皆對象,a=1背后是a=int(1),int是內置類,用的小寫,1也是一個對象,在堆里面分配內存
堆空間比棧空間大的多,二者都去找1,當然java找的快。
總結

java和python程序速度差一個量級是因為語言本身的特性,靜態和動態,與pyc文件毫無關系。
雖然速度差了一個量級,但是大多應用是無感知,0.1秒和0.01秒感覺都是過了1秒。
python是順序執行代碼的和if __name__=="__main__"無關。
一份程序為了區分主動執行還是被調用,Python引入了變量__name__,當文件是被調用時,__name__的值為模塊名,當文件被執行時,__name__為'__main__'
1 #/usr/bin/env/ python #(1) 起始行 2 #"this is a test module" #(2) 模塊文檔(文檔字符串) 3 import sys 4 import os #(3) 模塊導入 5 6 debug = True #(4) (全局)變量定義 7 class FooClass (object): 8 'foo class' 9 pass #(5) 類定義(若有) 10 def main(): 11 'test function' 12 foo = FooClass() 13 if debug: 14 print 'ran test()' #(6) 函數定義(若有) 15 if __name__ == '__main__': 16 main()
若是文件主動執行了,則最好寫成跟上面的例子一樣,main之前不要有可執行代碼,這樣做到程序從main()開始,流程邏輯性強
若是文件作為模塊被調用,則可以不用寫main(),從上而下順序執行。
其實Python是否保存成pyc文件和我們在設計緩存系統時是一樣的,我們可以仔細想想,到底什么是值得扔在緩存里的,什么是不值得扔在緩存里的。
在跑一個耗時的Python腳本時,我們如何能夠稍微壓榨一些程序的運行時間,就是將模塊從主模塊分開。(雖然往往這都不是瓶頸)
在設計一個軟件系統時,重用和非重用的東西是不是也應該分開來對待,這是軟件設計原則的重要部分。
在設計緩存系統(或者其他系統)時,我們如何來避免程序的過期,其實Python的解釋器也為我們提供了一個特別常見而且有效的解決方案。
