不要使用Python開發(fā)大型項目!
來源:CSDN(ID:CSDNnews)
整理 | 張仕影 責編 | 鄭麗媛
在很大程度上,Python并不像你想象的那么好。
在開發(fā)者的職業(yè)生涯中,有一個特定階段,開發(fā)者們會從為項目做貢獻到掌握自己的技巧。這個階段對有些人來說會來得早一些,有些則也會晚一些,而另一部分人則無法到達這個階段。
不過,大多數(shù)職業(yè)生涯較長的開發(fā)人員都經(jīng)歷過這個階段。我將這稱之為自己構(gòu)建的點。
如果你已經(jīng)到達了那個階段,你面對的第一個問題是:它是如何運作的?用戶體驗如何?架構(gòu)是怎樣的?數(shù)據(jù)如何流動?以及很多類似這樣的問題。
我在這里就不為你回答這些這些問題的答案了。無論你開始哪一個項目,它們都需要根據(jù)項目來高度定制,并且每一個都應該至少有一篇獨立的文章來解答。
不過,我想回答其中一個問題:哪種語言最適合這個項目?
你可能認為這取決于項目的類型,的確,但是每種編程語言都有一些缺陷。實際上,Python 也有很多缺陷,尤其是當你試圖用它來構(gòu)建一個大型程序的時候。
1、變量聲明不存在,這是一個問題
Python 之禪表明:顯式優(yōu)于隱式。但當涉及到變量聲明時,Python 中的隱式比顯式更常見。(注:Python 之禪指的是 Tim Peters 編寫的關(guān)于 python 編程準則。)
我們先來看看下面這一小段的 C 代碼:
char notpython[50] = "This isn't Python.";在對比 Python 之前,我們先來深入研究以下這個問題。
“char” 是一個類型標識符,表示之后的所有內(nèi)容都與一個字符串有關(guān)。“notpython” 是我給這個字符串取的名字。[50] 表示C將為此保留50個字符的內(nèi)存空間。不過,在本例中,我可以使用 19個 字符——每個字符一個,在末尾加上一個空字符\0,最后用一個分號巧妙地結(jié)束。
這種顯式聲明在 C 語言中是強制性的。如果你忽略它,編譯器則會罷工。
這種方式起初看起來既愚蠢又乏味,但是它的回報非常大。
兩周或兩年后讀 C 代碼的你突然發(fā)現(xiàn)了一個你不知道的變量,那么查一下聲明就可以。如果你給它起了一個有意義的名字,那這就會給你一個極大的提示:它是什么,它在做什么,以及哪里需要它。
對比一下 Python,你幾乎可以隨時創(chuàng)造變量。但如果你沒有給它取一個有意義的名字,或者至少留下一個關(guān)于它的注釋,那么將來可能會一團糟。
在 Python 中,除了深入研究代碼,否則很難理解變量在做什么。甚至如果你在變量中有一個拼寫錯誤,你可能會破壞整個代碼——Python 并沒有像 C 語言那樣的保護聲明。
如果你要處理的是較小的項目,或者不是很復雜的項目,比如說幾千行代碼,這就沒有什么問題 ,但如果是更大的項目…那就糟糕了。
你可以在 Python 中做顯式變量聲明,但只有一些非常勤奮的程序員才會這么做。當編譯器沒有問題時,很多程序員們往往會忘記這些額外的代碼行。
編寫 Python 是很快的,對于一些小而簡單的項目來說,閱讀 Python 也是很快的。不過閱讀和維護大型 Python 項目時,你最好成為找描述性變量名和注釋所有代碼的世界英雄,否則你就完了。
2、模塊,你屬于哪里?
如果你認為事情不會變得更糟糕,那么你就錯了。
變量從哪里開始“存在”于代碼中的問題不僅僅來自隱式聲明,還可能來自其他模塊,它們通常是以 my_module.my_variable() 這樣的形式存在。如果你被這樣一個變量搞糊涂了,那么當你檢查它出現(xiàn)在主文件中的其他位置時,你依舊會感到困惑。
你還必須檢查代碼中是否包含以下兩行代碼之一:
import my_modulefrom another_module import my_module
第二行的作用是告訴編譯器,你需要從一個包含更多內(nèi)容的模塊中獲得某個函數(shù)或變量。
這很煩人,因為有模塊比你在 PyPI 上找到的更多,還可以在計算機上導入任何其他 Python 文件。所以快速搜索你的函數(shù)和變量并非全然有益,甚至它可能會變得更糟糕。
模塊可以依賴于其他模塊。因此如果你不走運,你導入了模塊 A、 B 和 C,但是這些模塊依賴于模塊 E、 F 、G 和 H,而模塊 E、 F、G 和 H 又依賴于模塊 I、 J 和 K。突然之間,你需要管理的不僅是 3 個模塊,而是 10 個模塊。
更糟糕的是,有時候并不是這么簡單的結(jié)構(gòu)。比如說模塊 B 和 C 也取決于模塊 M 和 N ,而 J 也依賴于 M、C 和 H 依賴于 Q…后面不必多說,你懂的。
這是一個迷宮,也是一個由 Pythonians 創(chuàng)造且真實存在的依賴地獄。
循環(huán)依賴是迷宮中最丑陋的野獸。如果模塊 A 依賴于模塊 B,但模塊 B 同時使用模塊 A 的一部分…
在小項目中這并不是問題,但在大項目中……歡迎來到叢林。
3、大量依賴沖突
我要抱怨的不僅僅是模塊本身,還有它們的版本。
原則上, Python 有活躍的用戶群,很多模塊也會定期更新,這是非常好的。只是有一個問題:并非所有版本的模塊都能與其他模塊始終保持兼容。
比如說你使用模塊 A 和 B,而兩者都依賴于模塊 C。但是 A 需要3.2或者更高版本的 C, B 需要2.9或者更早的版本 C。
可你不在乎模塊 C,你只想要 A 和 B。
世界上沒有任何工具可以幫助你解決這場沖突。幸運的話,你會找到一個和你遇到同樣問題的人寫的補丁。如果你不是那么幸運,你將不得不編寫補丁。或者你用別的包。再或者,你可以重寫其中一個包(A 或者 B),然后在需要 C 的地方找到變通方法。
無論如何,你都需要額外的時間來解決問題。這就像在一個叢林里,你需要耐心以及一些導航工具來讓自己找到出路。
拋開依賴沖突,也有一些不錯的工具。像“pip”,它就可以很容易地安裝軟件包。使用一個簡單的“requirements . txt”,你可以指定哪些包以及你希望使用哪些版本等等。虛擬環(huán)境將所有包放在一個地方,并與主要 Python 分開安裝。
對于更大更復雜的項目,還有“conda”、 YAML 文件等等。但是你需要學習如何使用每一種工具,確保用最少的時間解決問題。
4、不同的機器,不同的 Python
即使你已經(jīng)解決了機器上的所有依賴問題,你的 Python 運行起來十分流暢,也不能保證它在其他機器上運行時依舊這么流暢。像“pip”、“ requirements . txt ”這樣的工具和虛擬環(huán)境可以幫助你瀏覽輕度依賴地獄,但僅限于本地。
在每臺新機器上,你都需要檢查并重新安裝各個版本及要求。
唯一簡便的解決方案是 Jupyter notebooks。在 Jupyter notebooks 中你可以用任何你喜歡的版本寫東西。在 Jupyter 中,一切都運行在一個在線服務器上,你可以將這些文件發(fā)送給任何人,他們同樣能夠使用它們。
不過這也有一個明顯的缺點:Jupyter 筆記本只有圖形界面。但是使用圖形界面,處理具有許多相互關(guān)聯(lián)文件的大型項目是相當困難的。
也許這就是為什么我很難在 Jupyter 筆記本上看到大項目的原因吧。
而其他語言只要有虛擬機,問題就解決了。
5、pip 之外的世界
假設(shè)你已經(jīng)設(shè)法通過使用 Jython 或 PyPy 或其他解決方案,將你的項目移植到不同的機器上(雖然這些比虛擬機處理起來要笨拙一些,但至少能奏效),接下來為了整合大型項目,你可能會集成 C 包、 Fortran 包等。這樣做有許多好處:C包可能不存在于 Python 中,而且通常更快。由于遺留原因,科學包往往只存在于 Fortran 中。
但實際上,在這過程中你必須使用像 ‘gcc’、‘gfortran’ 這樣的編譯器,甚至其它更多編譯器——這很麻煩,因為在 Python 代碼中集成 C 模塊的文檔超過 4500 字,集成 Fortran 的文檔也不短。
所以如果一開始就用 C 構(gòu)建整個項目可能會更好:雖然要慢一些,但是你可以避免必須使用多個編譯器和接口的情況。
C 很古老,幾乎任何東西都有包,甚至還有用戶友好的機器學習軟件包。
6、使用全局解釋器鎖鎖定性能
全局解釋器鎖(GIL),從 Python 誕生的第一天起就已經(jīng)存在,它使終端用戶的存儲管理變得非常容易。
至少在較小的項目中,開發(fā)人員在使用 Python 時根本不需要考慮計算機內(nèi)存。相比之下,在C中每個變量都保留了內(nèi)存位!
基本上,GIL 會計算一個變量在代碼中每個部分被引用了多少次。如果不再需要該變量,則會釋放它所占用的內(nèi)存空間。因此在小型項目中,GIL 有助于提高性能,因為不必要的內(nèi)存空間被清除掉了。但是在大項目中有一個問題:GIL 不喜歡多線程。
這是一種可以極大提高執(zhí)行程序性能的方式,其中多個指令線程在相同的進程資源上獨立運行。機器學習模型非常適合以這種方式進行訓練。
只有一個小問題:GIL 一次只能在一個線程上工作。因此,如果變量 A 在線程 1 上執(zhí)行,而線程 2 已經(jīng)完成了變量A ,那么它的內(nèi)存可能會被刪除,這取決于當時 GIL 處在什么位置。
正如你想象的那樣,這可能會導致非常奇怪的錯誤。
當然,這是有解決方法的,但它們都不是很完美,因為它通常不會像有 GIL 語言中的多線程那樣快。
7、并發(fā)和并行仍然笨重和混亂
我們已經(jīng)發(fā)現(xiàn)了并發(fā)的一個缺點,就是當你正在進行多線程時,全局解釋器鎖會減慢速度,或者導致奇怪的錯誤。
同樣的缺點也存在于 Python 的協(xié)同程序。
線程和協(xié)程有一些細微的區(qū)別,即協(xié)程一次執(zhí)行一個任務,而線程可以同時執(zhí)行多個任務。相同點在于,它們都是并發(fā)的實現(xiàn)。
當你有大量需要等待的任務時,協(xié)程很有用,比如你正在讀取網(wǎng)站數(shù)據(jù)并等待服務器響應。協(xié)程程序并不會讓計算機坐視不管,而是將另一個任務分配給它。另一方面,當你有幾個耗時的任務時,線程的優(yōu)勢就體現(xiàn)出來了,不太占用 CPU ,也不需要太多等待。
如果你有一個 CPU 密集型任務,并且想要充分利用你的硬件,那么并行是值得考慮的。
多處理也是個不錯的選擇,它會告訴計算機使用多個核心,節(jié)省時間。
不過,線程、協(xié)程和多處理這三種技術(shù)都面臨類似的問題,即在 Python 中實現(xiàn)它們并不難,但是代碼看起來很笨拙,很難讀懂,尤其是對于初學者。
像 Clojure 、 Go 和 Haskell 這樣的語言,在并行性和并發(fā)性方面要好得多。如果你處理的不是緩慢或密集型任務,就無需考慮。但如果你是,你可能要重新考慮你的選擇。
8、用什么代替Python
Python 自有其不可忽視的優(yōu)勢,但它也的確存在缺點。
如果你想要明確聲明的變量和開發(fā)良好的包,避免陷入依賴地獄,那么 C 就很不錯。
如果你想要的東西是可移植到任何機器的,那么 Java, Clojure 或 Scala 是很好的選擇。它們是在虛擬機上運行的,所以你不會遇到像 Python 一樣的麻煩。
而如果你想運行大型緩慢的任務,你可能會想試試 Go 或者 Haskell。一開始,它們比 Python 難學,但是你投入的時間是有回報的。
你還可以組合不同的語言,比如 Python 非常適合快速編寫腳本、草圖,甚至是中等規(guī)模的項目。我認識的許多開發(fā)人員都是用 Python 編寫第一稿和測試運行,然后用 C 、Go 或 Clojure 重寫重要部分。這可以使代碼執(zhí)行得更快,同時還可以享受 Python 提供的優(yōu)勢。
在大型項目中也并不是不能使用 Python,只是很多情況下它并不是唯一:你可以使用 Python 來拼湊 C 、 Go 或 Clojure 中的各個部分。
如果你已經(jīng)達到了自己構(gòu)建的目標,請記住,沒有任何一種語言是完美的。盡管 Python 有其缺點,但它依舊很棒,也很方便,你可以通過集成其他語言的代碼來避開 Python 這些缺點。
原文地址:https://thenextweb.com/news/dont-use-python-for-big-projects
程序員技術(shù)交流群
隨著讀者越來越多,我也建了幾個技術(shù)交流群,九分聊技術(shù),一分聊風雪,歡迎有興趣的同學加入我們。 可以長按識別下方二維碼,一定要注意:城市+昵稱+Python,根據(jù)格式,可以更快捷地通過選擇內(nèi)容進入群。 ▲長按掃描
閱讀更多
人生苦短,我用python
【神秘禮包獲取方式】
識別文末二維碼,回復:Python



