如何為使用 Python 語言而辯論

發表於2017-08-31

最近我寫了一篇關於我為什麼不擔心Python流失使用者的文章。幾分鐘之後有人問我Python的用法(usage),而這篇文章沒有提及,但卻是一個讓人深思的問題。我們看到,使用Python的使用者很可能在未來保持高位,但是Python是否會被用到儘可能多的專案中是不能保證的;使用者(users)數目很多而且穩定,但是專案中Python的用處(use)並不確定。

這篇文章的用意是幫助表明Python仍然對大多數軟體專案是切實可行的。我不擔心把Python推銷給反對其他動態語言(如Ruby)的人,因為我認為這些爭論與個人喜好有關。這篇文章是講給那些推銷靜態型別語言的人。具體上,這篇文章是針對Go的,但也可以是其他任何靜態型別語言。

”為什麼Go?”,你可能會問。因為Go實際上在獲取Python的使用者。當2003到2005年間Python的增長曲線是個曲棍球棒時,Python還不是被推下山巔的王者,而是個弱者。傳統上,Python從Java之類的語言陣營中獲得使用者,並且留住了他們(我不想談C++使用者,因為通常他們有嚴格的效能需求,需要一個系統語言,或者是效能成癮者,並且需要好好恢復)。但是Go的情況不太一樣。如今Python是使用最多的語言之一,而不再是弱者了。一旦在靜態型別語言社群中出現一門語言,它的生產效率/效能的取捨相當好,那便足以說服一些Python的程式設計師選擇Go而不再是Python了。

如今的Go

首先我應該說,Go是目前我第二喜歡的語言。如果今天我要啟動一個專案,但不能說服人們使用Python,那我會提議使用Go。不要誤解我在本文中說Go是門不好的語言。這篇文章的要點是說服其他人,Python是生產率/效能取捨遊戲中Go之外切實可行的替代方案,而不是表達Go是門不好的語言。認為這篇文章是反Go的,那就是你的個人想法,而且不應該這樣認為。

我應該說,我偶爾在工作中使用Go,並有點想關注這門語言的社群。既然我不能僅憑想象就成為Go專家,但這番話並不是僅從文件或者部落格中提取出來的。但是由於我是Python開發團隊的一份子,無論我如何試圖表現得公平,固有的偏見某種程度上還是有的。

那麼,帶著這些警告,我們來看下Go提供給開發者什麼。

生產率

我看待Go的方式是,使用你最喜歡的程式語言,移除那些難於加速生產率的特性,就是Go。靜態型別的影響被降到最小,因為通常只有在API邊界時你才會面對它。結構型別同樣使事情變得簡單(把它認為是鴨子型別)。語法並不笨拙(雖然它使用了花括號)。不要認為Go是C/C++去掉不安全的特性,加上生產率更高的東西,不然你會很失望(比如,“為什麼我不能使用make()內建函式,也不能像map型別一樣對返回值進行計數”,這種看待Go的方式是錯誤的;這就是為什麼C++開發者沒有轉到Go的原因)。快速編譯也使開發週期更像一個動態語言,而不是一個需要編譯的語言。而且事實上有些人喜歡沒有異常機制帶來的冗長,因為這促使你處理每種異常情形而不是(意外地)忽略它們(這是貫穿Go初始系統語言設計的例項)。還有,這門語言本身相當短小易記,並有嚴格的前向相容性要求(forward-compatibility requirements)(你不可能更快地獲得泛型),大體上使用Go來編碼是件很愉快的事情。

由於是靜態型別,Go可以很容易地獲得工具支援(它對之前以此為設計目標的語言也有幫助)。Go確保核心工具跟隨Go本身提供,也是明智之舉。go fmt強制執行Go風格的規則,並允許通過使用者自定義的規則來重構程式碼(“採用製表符縮排”不再是問題,因為這意味著你可以隨心所欲地設定編輯器來代表製表符,然後go fmt將其轉換為普通製表符以適用VCS)。go fix會更新程式碼以跟最新發布的版本保持一致。go get獲取依賴並安裝。

Go最後一個生產率功能是它靜態編譯所有東西,使部署更簡單。如果你使用容器來開發和部署,這也不算什麼。只有當你釋出單個檔案的命令列工具,而不是一組依賴和你自己的程式碼時,這才算得上事。

效能

就效能來說,Go做的很好。很難指出任何基準能準確的證明Go總是最快的選擇,甚至計算機語言基準遊戲一些基準證明CPython 3是最快的。但是通常情況下可以認為對於你的任何工作來說Go已經足夠快了。

Go真正出色的地方是併發性(concurrency) 。要注意併發程式碼並不是通常誤解的並行(parallelized)程式碼; 併發程式碼仍然可以是單執行緒的,僅僅在任務切換方面更加簡單/出色。Go通過使用goroutine使連續併發的程式碼執行起來絕對的簡單。如果你不想使用共享記憶體的方式(雖然也同樣支援),該語言提供的通訊管道允許以非常簡潔的訊息傳遞方式進行併發程式設計。將所有特徵整合進此語言中成為儘可能使用該語言開發併發程式碼的又一原因。換句話說,Go程式執行很快,該語言盡力使你在合理的方式上獲得該效果。

如今的Python

如果順利的話我已經讓你相信Go是一種優秀的程式語言,除非因為其他原因,一些人不會認為我在整篇文章對Go的描述很糟糕。現在我們討論一下Python的生產率/效能是怎麼樣的。

生產率

首先也是最重要的,Python非常容易學習。這也是為什麼在當前高評價的美國大學中Python作為首選的教學語言 。這相當於該語言擁有成熟穩定的新程式設計師的來源以及更容易培訓其他程式設計師。 我想,要說服別人只用幾行Python程式碼就會完成很多工作這並不難(Go/Python 3比較 顯示Python每次都比Go使用更少的程式碼完成相同的工作)。所以我會堅持認為使用Python會更高產,即使和Go相比,這不會有人反對。

通常大家反對Python的地方是在工具支援方面。但是如果你注意到我指出的Go相關的支援工具,fmt, fix, 和 get, Python社群也有對等的工具。對遵循PEP 8的風格格式化(style formating), 可以在提交檢查時使用pep8,或者如果想要更多go fmt風格的自動重寫可以使用autopep8。對用於重構的go fix或go fmt,你可以說2to3也可以完成同樣的功能。對於go get, Python有pip。我們有venv/virtualenvcx_Freeze這樣的程式碼凍結工具(跟其他一樣,位於容器之上?on top of containerization like anything else),而不是靜態編譯的二進位制包。甚至有貫穿專案的程式碼分析工具如pylint。說Python因為缺少工具支援而不能用於大型專案,這種觀點對我來說是很膚淺的。

如果說有哪方面Python完全做的好,那就一定是它豐富的第三方擴充套件庫和相應的工具可供使用,就像在PyPI上面看到的那樣(我相信肯定有人忍不住要爭論說,“並不是所有的第三方庫都能夠在Python3上面執行啊”,事實確實如此,然而,這些第三方擴充套件庫對Python3的支援已經相當好了,而且還在繼續改善中,所以我不會太在意這個爭論,另外,你可以同時使用Python2/3兩個版本進行編碼,不需要關心針對哪個版本)。看一下godoc.org,上面顯示Go也並不缺少社群支援,Pytho之所以能夠擁有更多可用的第三方庫僅僅是因為它的年齡,這個狀態也會繼續持續。

效能

因為Python已經存在很久,且變得如此龐大, 簡單地去說 “Python是足夠快的” 不能說明整個的情況, 那是因為有各種各樣的實現加速的方式。但是在深入到VM級別的選項之後,意味著Python的stdlib提供了獲得加速的選項。舉例來說, concurrent.futures 是尷尬地執行並行程式碼的方式,這種方式是極其簡單的。而在Python 3.3中,新的asyncio編寫了非同步程式碼。它沒有像Go那樣被整合進語言,在Python中的併發程式設計是可行的,且在方式上也未必是那麼痛苦的。

但是最好的辦法是,你可以在選擇的VM裡改變Python程式碼的效能。

CPython + Cython

如果你在使用 C 擴充模組,CPython 就會使你最好的選擇(可能你不知道這個術語,CPython 是你可以在 python.org 獲得的直譯器)。對大多數的情況而言效能至少合理些– 因為某些原因,一些人認為Python 開發團隊不關心效能,這個一個謊言 – 而且即將會成為新的特性,因為 CPython 同時擔當著語言規範的作用。

如果認為你的一些內迴圈程式碼確實需要提高些速度, Cython 是 CPython 的選擇。Cython 會盡可能的將你的 Python 程式碼編譯成 C 擴充程式碼。有若干種支援的方法可以產生更好的 C 程式碼,所以這取決於你需要怎樣的 Cython 特性。Cython 同時也使寫出 C 擴充模組更加簡單(但要繼續讀下去,除了CPython 還有其他的選擇)。

PyPy + cffi

如果你不依賴於已存在的 C 擴充模組,PyPy 會給你提供總體上最好的效能。它的JIT非常好而且它的團隊歡迎受到使用 CPython 並且執行更快的程式碼的挑戰,因為他們痛恨在 speed.pypy.org 中顯示的那麼慢。實話說,除非PyPy不支援你真的想用的那個版本的 Python – 因為 PyPy 確實會落後2個版本, 比如pypy3 現在支援 Python 3.2 然而 3.4 是最新的 CPython 釋出版; 它們期盼在這個問題上能得到幫助(donation) – 我只能考慮不使用PyPy因為你依賴於已存在的C擴充模組C( numpy 是最常見的問題,雖然 PyPy is looking for donations 可修復這個問題)。

但這不意味著,如果想封裝一些C程式碼就用不了PyPy。PyPy專案還有另外一個子專案cffi,這個專案的目的是使Python程式碼也可以利用封裝的C程式碼。使用cffi的關鍵好處在於,一旦你使用了cffi,C程式碼就可以用於CPythonPyPy(我認為IronPython和Jython也在新增對cffi的支援)。所以如果你在封裝C程式碼,我強烈建議你看下cffi,而不是手動寫C擴充套件模組或者使用Cython,這樣你有更好的Python實現的支援,還能使用PyPy。

Numba

如果你在做數值相關的工作,你肯定應該考慮Numba這個選擇。在科學計算上,經濟學家注意到了它的效能。雖然在普通的Python程式設計上,它不能幫到什麼忙,但是如果在Python非常強大的科學計算棧中用到了numpy或者其他模組,Numba使用LLVM來進行JIT肯定會有幫助。

未來的 Python

考慮到所有內容,Python肯定不是停滯不前的(Go也沒有,比如它們都在忙於用Go重寫編譯器和將聯結器以外的東西轉移進編譯器來獲得更快的編譯速度)。Python 的未來看起來還是光明的。

生產率

Python 是一種在進化的語言。不像 Go,Python 樂於改變該語言,甚至是以永遠不再向後相容的方式。這意味著Python會比Go更快的速度變得更加高效(雖然在Go 2開發之前Go的團隊對該語言進化持哪種觀點還是未知的)。

在工具方面,標準化的 函式註解 是為了宣告型別。 這是在 PyCon 2014 語言峰會期間提出的,針對函式引數和返回值,裡面提到有大量的專案現在想要有一種宣告預期型別的方法,使用函式註解考慮到了在某些方面的標準化,最終可能對標準庫函式也會是有用的。在pytypedecl的郵件列表上的討論還沒有開始,但是我知道PEP大概是要開始了。不僅僅是對像Cython和Numba這樣的專案在什麼地方使用列印資訊,還包括在諸如程式碼分析,重構等的時候使用。

效能

長遠看來,有兩個專案可以幫助提升Python的效能。一個是新的Python虛擬機器Pyston。儘管Pyston剛出現時間不長,但它的目標是要使用LLVM的JIT(是的這不由得讓人想起Unladen Swallow,然而LLVM的JIT已經比它在2009年時好很多了,所以這個專案還是頗有希望取得好效果的)。

其實PyPy-STM才是真正能夠讓我興奮的專案,”STM”表示”軟體事務性記憶體”,它基本上是允許Python丟棄GIL的。PyPy-STM此時的效能比PyPy要慢大約1.2-3倍,這樣的表現已經相當不錯了。目前他們正在尋找資助,來繼續這項工作,要實現這個目標:使得帶有兩個執行緒的PyPy-STM值得在PyPy上普遍執行。

在黑暗中做出選擇

希望這篇部落格傳達的不是一個總結, 而是一個關於生產力/效能折衷的方案。 Python已經清晰地擁有了強大的生產力輔助並且沒有哪個領域表現不佳,這仍是我選擇的語言。如果你發現自己有可能選擇Python專案以外的東西,請一定要停下來思考沒有使用Python所帶來的生產力損失,然後看看你有各種選項讓Python加快執行,這樣你再去做一個全面的關於Python是否可以為你的專案工作的選擇。

相關文章