選自hackerfactor,做者:Neal Krawetz,機器之心編譯。php
本文做者有一羣 geek 朋友,常常一塊兒討論技術話題,有時候也會談到編程語言。「I hate Python」,做者表示。他對 Python 厭惡至極。即便有現成的 Python 代碼可用,他也寧願用 C 語言重寫。爲了系統地吐槽 Python,做者專門寫了這篇博客,細數 Python 的「八宗罪」。html
這個話題已經在 Hacker News 上引起了熱烈的討論(評論 400+),感興趣的讀者能夠去圍觀或參與一下。python
Hacker News 討論:news.ycombinator.com/item?id=187…程序員
若是要安裝一個默認的 Linux 操做系統,那你頗有可能須要安裝多個版本的 Python:Python二、Python3 甚至是 3.五、3.7。緣由在於:Python3 沒法與 Python2 徹底兼容。甚至一些用小數表示的版本(如 3.五、3.7)也明顯缺少向後的兼容性。正則表達式
我徹底同意往編程語言中添加新的功能,我甚至不介意淘汰一些舊的版本。但 Python 卻要分開安裝。個人 Python 3.5 代碼不適用於 Python 3.7 安裝版本,除非我特地將其導入 3.7。不少 Linux 開發者都以爲導出太麻煩,所以安裝 Ubuntu 的時候會一併安裝 Python2 和 Python3——由於有的核心功能須要前者,而有的須要後者。npm
向後兼容性的缺少和各自爲政的版本一般會爲其敲響喪鐘。Commodore 創造了第一批家用電腦(比 IBM PC 和蘋果都要早不少)。但 Commodore PET 不能與後續的 Commodore CBM 兼容。CBM 又不與 VIC-20、Commodore-6四、Amiga 等兼容。所以,你要麼選擇花不少時間將代碼從一個平臺導到另外一個,要麼選擇放棄這個平臺。(Commodore 今天何在?早被用戶拋棄涼涼了……)編程
相似地,Perl 也火過一陣。但 Perl3 與 Perl2 的不少代碼也不兼容。社區罵聲一片,因而一些好的代碼導了出來,其餘的則被拋棄了。Perl4 也是如此。等 Perl5 出來的時候,人們乾脆改用另外一種更穩定的編程語言。現在,只有一小部分人還在頻繁使用 Perl 來維持以前的項目。但已經沒有人用 Perl 建立新的大項目了。數組
同理,Python 的每一個版本也都存在穀倉效應。以前的版本還要留着,最終形成手裏有一堆舊的無用 Python 代碼,由於你們都不想花時間將其移到最新版上。據我所知,沒有人爲 Python2 建立新代碼了,但咱們還留着它,由於沒有人想將所需代碼移到 Python3.x 中。Python 2.七、3.五、3.六、3.7 的文檔都還在 Python 官網上積極維護着,由於他們沒法下決心棄用以前的代碼。Python 就像一種殭屍編程語言——已經死掉的部分還在以行屍走肉的方式存在着。瀏覽器
不少軟件包均可以幫你輕鬆地運行 apt、yum、rpm 或其餘一些安裝庫,並得到最新版本的代碼。但 Python 並不是如此。若是用「apt-get install python」安裝,你都不知道本身安的是哪一個版本,它可能也沒法與你所需的全部代碼兼容。機器學習
所以,你要安裝你須要的那版 Python。個人其中一個項目用到 Python,但必須用 Python3.5。因此最後,個人電腦安裝了 Python二、Python2.六、Python3 及 Python3.5。其中兩個來自操做系統,一個用於項目,另一個服務於出於其餘緣由安裝的無關軟件。雖然都是 Python,但此 Python 非彼 Python。
若是你想安裝 Python 包,你應該使用「pip」(Pip Installs Packages)。但因爲系統上有一堆 Python,你要注意使用正確版本的 pip。不然,「pip」可能運行「pip2」,而不是你須要的「pip3.7」。(若是名稱不存在,你須要爲 pip3.7 指定明確的真實路徑)
一位隊友建議我配置本身的環境,這樣的話每種軟件均可以使用 Python3.5 的 base 環境。在我須要用 Python3.6 開展另外一個項目以前,這種作法是很是行得通的,可是須要 Python 3.6 就得建立另一個環境。兩個項目,兩版 Python,一點都不會混,真的(用生命在假笑)。
pip 安裝程序將文件放置在用戶的本地目錄。安裝系統級的庫時不用 pip。Gawd 不容許你在運行「sudo pip」時出錯,由於那會毀了你的整個電腦!運行 sudo 可能會使一些軟件包在系統級別安裝,有些是爲錯誤版本的 Python 安裝的,而你的主目錄中的一些文件可能最終歸 root 全部,所以將來的非 sudo pip 安裝可能會因權限問題而失敗。不要這樣作。
這些 pip 模塊由誰來維護呢?固然是社區。也就是說,沒有明確的全部者,也沒有強制性的來源鏈或責任鏈。今年早些時候,PyPI 的一個版本中發現了一個竊取 SSH 憑證的後門。這也是意料之中。(出於一樣的緣由,我不用 Node.js 和 npm;我不信任他們的社區項目。)
我很是主張代碼的可讀性要強。乍一看,Python 的可讀性彷佛不錯。但當你開始建立大型代碼庫的時候你就不會這麼想了。
大多數編程語言使用某種符號來標識範圍——函數的開始和結束位置、條件語句中包含的操做、變量的定義範圍等。C 語言、Java、JavaScript、Perl 和 PHP 都用 {...} 來定義範圍,Lisp 使用 (...)。Python 呢?它用空格!若是你要定義複雜代碼的範圍,你能夠縮進接下來的幾行代碼,縮進結束時,該範圍也截止。
Python 手冊說,你能夠用任意數量的空格或製表符來定義範圍。可是,每次縮進最好使用四個空格!若是你想縮進兩次進行嵌套,使用八個空格!Python 社區已經對此進行標準化,即便 Python 手冊中並無明文規定。這個社區就喜歡用四個空格。因此,除非你不打算將本身的代碼向任何人展現,不然的話每次縮進最好用四個空格。
我第一次看到 Python 代碼時,以爲用縮進來定義範圍還挺好的,但這麼作有一個巨大的缺陷。你能夠進行深度嵌套,但這麼作使得每一行都會很長,致使不得不在文本編輯器中換行。較長的函數和條件語句可能會使開始和結束範圍很難匹配。並且當你不當心把三個空格當成四個空格,還容易出現計算錯誤,進而花幾個小時來調試和追蹤。
對於其它語言,我已經養成了調試代碼不帶任何縮進的習慣。這樣,我能夠快速瀏覽代碼,而後輕鬆地識別和刪除調試代碼。但 Python 呢?任何沒有適當縮進的代碼都會產生縮進錯誤。
大部分編程語言都有辦法導入其它代碼塊。好比,C 語言用「#include」,PHP 語言能夠用「include、include_once、require、require」。而 Python 用的是「import」。
Python 能夠導入整個模塊、模塊的一部分或模塊中的特定函數。C 語言?你能夠查看「/usr/include/」。Python 的話,最好用「python -v」列出全部路徑,而後從列表中搜索每一個目錄和子目錄中的每一個文件。我有些朋友很喜歡 Python,但我看到他們想導入東西時,總得瀏覽標準模塊。
導入功能還容許用戶重命名導入的代碼。它們基本上定義了一個自定義的命名空間。乍一看,你會以爲挺不錯的,但這最終會影響可讀性和長期支持。重命名對於較小的腳原本說仍是不錯的,但對於長期項目來講真的不適用。那些使用 1-2 個字母做爲命名空間(好比「import numpy as n」),並且還不按約定俗成的方式來命名的,簡直應該拉出去槍斃!
這還不是最糟糕的。大部分編程語言 include 代碼的時候就只是導入代碼而已。若是有一個帶有構造函數的全局對象,有些語言,如面向對象的 C++可能會執行代碼。相似地,有些 PHP 代碼可能會定義全局變量,因此導入能夠運行代碼——但這種作法一般被認爲很糟糕。相比之下,不少 Python 模塊包含在導入期間運行的初始化函數。你不知道在運行的是什麼,它要幹什麼,你甚至可能不會注意到。除非存在命名空間衝突,若是這樣就好玩了,你得花不少時間來尋找緣由。
在其它語言中,數組(array)直接稱之爲'arrays',可是在 Python 中,它們被稱爲 'lists'。關聯數組在某些地方被稱爲 'hash' (Perl),可是 Python 將其稱爲「字典」(dictionary)。Python 彷佛徹底按照本身的節奏來,不使用計算機科學和信息科學領域的常見術語。
此外,Python 庫的命名也有問題。PyPy、PyPi、NumPy、SciPy、SymPy、PyGtk、Pyglet、PyGame……(前兩個庫的發音同樣,可是它們的功能徹底不一樣)。我理解「py」表示 Python,可是它們就不能統一出如今前面或後面嗎?
一些常見庫放棄了相似雙關語的「Py」命名約定,包括 matplotlib、nose、Pillow和 SQLAlchemy。雖然有一些命名可能暗示其目的(如 SQLAlchemy 包含 SQL,因此它多是一個 SQL 接口),可是其它的可能只是隨機的單詞。若是你不知道「BeautifulSoup」這個庫是幹什麼的,那麼你能從命名看出來它是一個 HTML/XML 解析器嗎?不過,BeautifulSoup 有很完善的文檔且易於使用,若是每個 Python 模塊都這樣,我也就不抱怨了,可是大多數 Python 庫的文檔很是爛。
總的來講,我認爲 Python 是一個具備不一致命名約定的函數庫集合。我常常抱怨開源項目的命名很是可怕。除非你知道這些項目在幹什麼,不然你從命名自己中什麼都看不出來。除非你知道在尋找什麼樣的庫,否則只能經過別人偶然說起的名字或偶然的機會發現一些庫。大多數 Python 庫加劇了這種現象,也加劇了 Python 的負面體驗。
每種語言都有本身比較奇特的操做。C 語言中使用 & 和 * 獲取地址空間和值的命名法很是奇怪。C 語言中還有用 ++ 和—實現 increment/decrement 的捷徑。Bash 語言中,在引用特定字符(如用於正則表達式的圓括號和句號)時須要一直考慮「何時使用轉義符 (\)」。JavaScript 兼容性有問題(並不是每一個瀏覽器都支持全部有用的功能)。但 Python 的奇怪操做比我見過的其餘語言都多。如:
在 C 語言中,雙引號裏的是字符串,單引號裏的是字符。
在 PHP 和 Bash 中,兩種引號都能包含字符串。可是,雙引號裏的字符串能夠嵌入變量。相比之下,單引號的字符串是文字;任何嵌入的相似變量的名稱都不可擴展。
在 JavaScript 中,單引號和雙引號沒什麼區別。
在 Python 中,單引號和雙引號也沒有什麼區別。可是,若是你想讓字符串跨行,就得用三重引號,如"""string""" 或 '''string'''。若是你想用二進制,那你須要優先選擇帶有 b(b'binary')或 r(r'raw')的字符串。有時你要用 str(string) 把字符串轉換爲字符串,或使用 string.encode('utf-8') 將其轉換爲 utf8 格式。
若是你一開始認爲 PHP 和 JavaScript 中的=、==、===有點奇怪,那等你用 Python 中的引號時可能不會這麼想了。
大多數編程語言的函數參數傳遞是傳值。若是函數改變了值,結果不會傳遞迴調用代碼。但正如我解釋過的,Python 恰恰要有所不一樣。Python 默認使用 pass-by-object-reference 來傳遞函數參數。這意味着改變源變量可能最終會改變值。
這是面向程序、函數和對象的編程語言之間的最大區別。若是每一個變量都由對象引用來傳遞,而且變量的任何變化都會改變全部的引用,那你可能使用的都是全局對象。經過不一樣的命名調用相同的對象不會改變對象,因此實際上它就是全局的。此外,正如 C 的程序員早就學到的,全局變量太噁心了,別用。
在 Python 中,你必須經過值來傳遞變量,例如「a=b」只是給相同的對象空間分配了另外一個命名,但並無複製 b 的值給 a。若是你真的想要複製 b 的值,你須要使用一個 copy 函數,一般是「a=b.copy()"的形式。然而,注意我說的是「一般」。不是全部數據類型都有一個「copy」原型,或者 copy 函數多是不完整的。在這種狀況下,你可使用單獨的「copy」庫:"a=copy.deepcopy(b)"。
用所用的庫或函數的名字來命名程序是常見的編程技巧。例如,若是我用一個叫作「libscreencapture.so」的 C 庫來測試一個截屏程序,我會將該程序命名爲「screencapture.c」並編譯爲「screencapture.exe」。
gcc -o screencapture.exe screencapture.c -lscreencapture
在 C、Java、JavaScript、Perl、PHP 等語言中,這一般頗有效,由於這些語言能夠輕易地辨別本地程序和資源庫,它們有不一樣的路徑。但 Python 呢?仍是算了吧,千萬別這樣作。爲何?Python 會假定你首先要導入本地代碼。若是我有一個名爲「screencapture.py」的程序使用了「import screencapture」,那麼它將導入本身而不是系統庫。至少,你應該調用本地程序「myscreencapture.py」吧。
Python 是一門很是流行的編程語言,有不少粉絲。甚至個人不少朋友都很喜歡 Python。多年來,我和他們討論過這些問題,每次他們都點頭表示贊成。他們並不反對 Python 存在這些問題,只是認爲這不足以澆滅他們對這種語言的熱情。
個人朋友常常提到那些很是酷的 Python 庫。我贊成一些庫很是有用。例如,BeautifulSoup 是我用過最好的 HTML 解析器之一,NumPy 使多維數組和複雜的數學更容易實現,而 TensorFlow 對於機器學習很是有用。可是,我不會由於喜歡 TensorFlow 或 SciPy 而用 Python 建立單片程序。我不打算爲了這些「蠅頭小利」而放棄可讀性和可維護性,這不值得。
一般當我寫一篇關於某個主題的批評時,我也會嘗試寫一些積極的東西。但我沒辦法列出關於 Python 的好的方面,由於我真的認爲 Python 很糟糕。