由Python歷史「解密」Python底層邏輯

An楠發表於2019-06-24

一次純粹的hacking

Python的作者,Guido von Rossum,荷蘭人。1982年,Guido從阿姆斯特丹大學獲得了數學和計算機碩士學位。儘管,他算得上是一位數學家,但他更加享受計算機帶來的樂趣,熱衷於做任何和程式設計相關的活兒。

80年代,掀起了個人電腦浪潮,但受限於個人電腦配置低,所有的編譯器的核心是做優化,以便讓程式能夠執行。在那個時代,程式設計師恨不得用手榨取計算機每一寸的能力。有人甚至認為C語言的指標是在浪費記憶體,至於動態型別,記憶體自動管理,物件導向…… 別想了,那會讓你的電腦陷入癱瘓。

而這種程式設計方式讓Guido感到苦惱。Guido知道如何用C語言寫出一個功能,但整個編寫過程需要耗費大量的時間。

不過,他還有另一個選擇shell。shell可以像膠水一樣,將UNIX下的許多功能連線在一起。UNIX的管理員們常常用shell去寫一些簡單的指令碼,以進行一些系統維護的工作,比如定期備份、檔案系統管理等等。然而,shell的本質是呼叫命令,並不能全面的調動計算機的功能。

Guido希望有一種語言,這種語言能夠像C語言那樣,能夠全面呼叫計算機的功能介面,又可以像shell那樣輕鬆的程式設計。

**ABC語言讓Guido看到希望。**ABC是由荷蘭的數學和計算機研究所開發的,Guido在該研究所工作,並參與到ABC語言的開發。ABC 語言是一個致力於為初學者設計程式設計環境的長達 10 年的研究專案,與當時的大部分語言不同,ABC語言的目標是“讓使用者感覺更好”。

比如下面是一段來自Wikipedia的ABC程式,這個程式用於統計文字中出現的詞的總數: HOW TO RETURN words document: PUT {} IN collection FOR line IN document: FOR word IN split line: IF word not.in collection: INSERT word IN collection RETURN collection

HOW TO用於定義一個函式。一個Python程式設計師應該很容易理解這段程式。ABC語言使用冒號和縮排來表示程式塊。行 尾沒有分號。for和if結構中也沒有括號() 。賦值採用的是PUT,而不是更常見的等號。這些改動讓ABC程式讀起來像一段文字。

儘管ABC已經具備了良好的可讀性和易用性,但最終卻也沒能流行起來。原因在於:

  • 硬體上的困難:ABC語言編譯器需要比較高配置的電腦才能執行,而當時電腦使用者,更多考慮程式效率,而非語言難度;
  • 一個語言設計的致命問題:其可擴充性較差,如果想在ABC語言中增加功能,比如對圖形化的支援,就必須改動很多地方。
  • 不能直接進行IO:ABC語言不能直接操作檔案系統。儘管你可以通過諸如文字流的方式匯入資料,但ABC無法直接讀寫檔案。

輸入輸出的困難對於計算機語言來說是致命的。你能想像一個打不開車門的跑車麼?

ABC的前車之鑑,給Guido帶來啟示。

1989年,為了打發聖誕節假期,Guido開始寫Python語言的編譯器。Python這個名字,來自Guido所摯愛的電視劇Monty Python's Flying Circus。他希望這個新的叫做Python的語言,能符合他的理想創造一種C和shell之間,功能全面,易學易用,可擴充的語言。Guido作為一個語言設計愛好者,已經有過設計語言的嘗試。這一次,也不過是一次純粹的hacking行為。

Python直譯器的誕生

1991 年,第一個 Python 直譯器誕生,它是用 C 語言實現的,並能夠呼叫 C 語言的庫檔案。從一出生,Python已經具有了:類,函式,異常處理,包含表和詞典在內的核心資料型別,以及模組為基礎的擴充系統。

這裡需要牽扯一個“編譯器”的概念,其主要作用是便於人編寫,閱讀,維護的高階計算機語言翻譯為計算機能識別,執行的低階機器語言的程式。

編譯器翻譯語言方式有2種:編譯、解釋。 enter image description here

①編譯型語言:需通過編譯器(compiler)將原始碼編譯成機器碼,之後才能執行的語言。

一般需經過編譯(compile)、連結(linker)這兩個步驟。編譯是把原始碼編譯成機器碼,連結是把各個模組的機器碼和依賴庫串連起來生成可執行檔案。

②解釋型語言:解釋性語言的程式不需要編譯,相比編譯型語言省了道工序,解釋性語言在執行程式的時候才逐行翻譯。

Python是一種解釋型語言,它的原始碼不需要編譯,可以直接從原始碼執行程式。Python直譯器將原始碼轉換為位元組碼,然後把編譯好的位元組碼轉發到Python虛擬機器(Python Virtual Machine,PVM)中執行。 enter image description here

當我們執行Python程式碼的時候,在Python直譯器用四個過程“拆解”我們的程式碼:

  • 首先,當你把鍵入程式碼交給Python處理的時候會先進行詞法分析,如果你鍵入關鍵字或者當輸入關鍵字有誤時,都會被詞法分析所觸發,不正確的程式碼將不會被執行。
  • Python會進行語法分析,例如當"for i in test:"中,test後面的冒號如果被寫為其他符號,程式碼依舊不會被執行。
  • 進入最關鍵的過程,在執行Python前,Python會生成.pyc檔案,這個檔案就是位元組碼。
  • 將編譯好的位元組碼轉發Python虛擬機器中進行執行:由Python Virtual Machine(Python虛擬機器)來執行這些編譯好的位元組碼。

什麼是位元組碼(bytecode)?

簡單的說它就是一個從原始碼編譯而來的中間檔案(用於不同作業系統平臺的直譯器執行)。比如,a說日語,b說中文,溝通起來不暢通,請一個翻譯,把a和b的語言都翻譯成英語,這個英語就可以理解成bytecode, 一種中間語言。

bytecode的好處就是 載入快,而且可以跨平臺, 同樣一份bytecode,只要有作業系統平臺上有相應的Python直譯器,就可以執行,而不需要原始碼。不同版本的Python編譯的位元組碼是不相容的,Python 2.6編譯的bytecode拿到Python 2.7上去執行就不行了。

如何生成位元組碼?

Python直譯器一般會自動把.py檔案轉換成bytecode,然後再執行它。當你第一次把.py檔案當作module匯入,或者對應的.py檔案比.pyc檔案的修改時間還要新時,Python直譯器都會再從source code生成相應的新bytecode。這樣當你下次再次執行程式時,就會直接從bytecode執行,從而節省便宜時間。

Ps:這裡需要注意,有些情況bytecode並不會生成:

  • 遇到目錄寫許可權的問題時。(比如你編寫程式碼和執行程式碼使用的具有不同許可權的使用者角色,Linux上很常見)
  • 執行一個script並不會被當成是import操作,所以可能也不會生成bytecode。(比如:你有個一個a.py的檔案,其中在a.py裡,你import了b.py,那麼執行python a.py後,會生成b.pyc,而不會生成a.pyc)

☞擴充閱讀:

(下文詳細說明Python的工作機制和Python虛擬機器內幕)

Python 位元組碼介紹

.pyc檔案是什麼?

Python原始碼編譯的結果就是 PyCodeObject ,每個作用域會編譯出一個對應的程式碼物件,其中名為co_code的PyStringObject儲存著程式碼物件的位元組碼。

一個Python原始檔就是一個模組。每個模組頂層的程式碼物件通過marshal序列化之後就得到了.pyc檔案。marshal以little-endian位元組序來序列化資料。

那巢狀於頂層作用域裡面的那些作用域,例如函式、類的定義,它們對應的程式碼物件在哪裡?它們每一個都乖乖的躺在上一層作用域的程式碼物件的co_const(常量池)域裡,所以其實頂層程式碼物件已經巢狀包含了底下其它作用域的程式碼物件。

☞擴充閱讀:

(下文主要結合例項說明了.pyc檔案結構)

Python 2.6.2的.pyc檔案格式

如何對.pyc檔案檔案進行反編譯?

python檔案如果要釋出的話,有時候還是難免想保護一下自己的原始碼,有些人就直接編譯成了pyc檔案,因為這樣既可以保留跨平臺的特性,又可以不能直接看到程式碼,也看到網上很多人說為了保護自己的程式碼可以編譯成pyc檔案。

用pyc檔案可以保護python程式碼的想法其實是不正確的 ,pyc檔案是可以很容易被反編譯的,比如說比較著名的uncompyle6 庫(https://github.com/rocky/python-uncompyle6),用來反編譯檔案最爽不過了,幾乎支援python全版本的pyc檔案的反編譯。

為什麼要做程式碼分析?

一般來說,程式碼分析重要性的判斷比較主觀,不同的人有不同的認識。Python是用C來實現的,所以對於Python的效能或程式碼質量的評估可以通過 dis模組 獲取到對應的位元組碼指令來進行評估。

一般來說一個Python語句會對應若干位元組碼指令,Python的位元組碼是一種類似彙編指令的中間語言,但是一個位元組碼指令並不是對應一個機器指令(二進位制指令),而是對應一段C程式碼,而不同的指令的效能不同,所以不能單獨通過指令數量來判斷程式碼的效能,而是要通過 檢視呼叫比較頻繁的指令的程式碼 來確認一段程式的效能。

一個Python的程式會有若干程式碼塊組成,例如一個Python檔案會是一個程式碼塊,一個類,一個函式都是一個程式碼塊,一個程式碼塊會對應一個執行的上下文環境以及一系列的位元組碼指令。

dis模組主要是用來分析位元組碼的一個內建模組。dis 模組的文件 可以讓你遍歷它的內容,並且提供一個位元組碼指令能夠做什麼和有什麼樣的引數的完整清單。

☞擴充閱讀:

(下文主要說明了dis模組的使用)

Python反編譯之位元組碼

Python開發者如何寫出高質量的程式碼?

要不這樣吧,如果程式語言裡有個地方你弄不明白,而正好又有個人用了這個功能,那就開槍把他打死。這比學習新特性要容易些,然後過不了多久,那些活下來的程式設計師就會開始用 0.9.6 版的 Python,而且他們只需要使用這個版本中易於理解的那一小部分就好了(眨眼)。

—— Tim Peters

傳奇的核心開發者,“Python 之禪”作者

給 comp.lang.python Usenet 小組的留言,2002 年 12 月 23 日,“Acrimony in c.l.p”。

Python 官方教程的開頭是這樣寫的:“Python 是一門既容易上手又強大的程式語言。”這句話本身並無大礙,但需要注意的是,正因為它既好學又好用,所以很多 Python 程式設計師只用到了其強大功能的一小部分。

只需要幾個小時,經驗豐富的程式設計師就能學會用 Python 寫出實用的程式。然而隨著這最初高產的幾個小時變成數週甚至數月,在那些先入為主的程式語言的影響下,開發者們會慢慢地寫出帶著“口音”的 Python 程式碼。與此同時,你會發現,自己在持續陷入基本的熟練程度,卻無從提升自己的程式設計技能。

其實,掌握Python程式設計不僅要掌握該語言的理論方面, 理解和採用社群使用的慣例和最佳實踐也同樣重要。 而且這些技巧可以很好的幫助你避免重複勞動,寫出簡潔、流暢、易讀、易維護的程式碼。

☞擴充資料:

《流暢的Python》

  • PSF研究員、知名PyCon演講者心血之作
  • Python核心開發人員擔綱技術審校
  • 全面深入,對Python語言關鍵特性剖析到位
  • 大量詳盡程式碼示例,並附有主題相關高質量參考文獻和視訊連結
  • 兼顧Python 3和Python 2

本書致力於幫助Python開發人員挖掘這門語言及相關程式庫的優秀特性,寫出簡潔、流暢、易讀、易維護的程式碼。特別是深入探討了針對資料庫處理時生成器的具體應用、特性描述符(ORM的關鍵),以及Python式的物件:協議與介面、抽象基類及多重繼承。

《深入理解Python特性》

  • 上市兩個月獲 Amazon 百餘條五星評價
  • 影響全球1 000 000以上程式設計師的PythonistaCafe社群創始人Dan Bader手把手帶你提升Python實踐技能
  • 與《流暢的Python》互為補充,Python進階必備

本書致力於幫助Python開發人員挖掘這門語言及相關程式庫的優秀特性,避免重複勞動,同時寫出簡潔、流暢、易讀、易維護的程式碼。用好Python需要了解的最重要的特性、Python 2過渡到Python 3需要掌握的現代模式、有其他程式語言背景想快速上手Python的程式設計師需要特別注意的問題,等等,本書都可以解決。

參考資料:

https://blog.csdn.net/miaodalengshui/article/details/77451262

https://mp.weixin.qq.com/s/qqHQYyqFsCYVIYjmWOF4jQ

https://linux.cn/article-9816-1.html

https://blog.csdn.net/helloxiaozhe/article/details/78104975

https://www.cnblogs.com/mlgjb/p/7899534.html

enter image description here

相關文章