一次純粹的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