程式語言排行榜第一Python,為何頻繁遭受開發者的嫌棄!
版本
預設的 Linux 安裝很可能會帶有多個版本的 Python。很可能會同時擁有 Python 2 和 Python 3,而且很可能同時擁有不同的子版本,如 3.5 和 3.7。理由是:Python 3 不能與 Python 2 完全相容。即使一些子版本號也會造成無法後向相容。
我不反對給語言新增新功能,甚至退役一些舊版本也無所謂。但是,不同的軟體需要不同的 Python。我給 Python 3.5 編寫的程式碼不能在 Python 3.7 上正常執行,除非我專門將其移植到 3.7。許多 Linux 開發者都認為移植不值得,所以 Ubuntu 就同時帶有 Python 2 和 Python 3,因為不同的核心功能需要不同的 Python 版本。
缺乏向後相容和版本之間的割裂通常是死亡的訊號。Commodore 是世界上最早製造家用電腦的廠商之一(遠在 IBM PC 和蘋果之前)。但 Commodore 的 PET 沒法與後繼的 Commodore CBM 電腦相容。而且,CBM 也不相容 VIC-20、Commodore-64、Amiga 等。所以,你只能花大把時間把程式碼從一個平臺移植到另一個平臺,否則就要完全放棄那個平臺。(現在 Commodore 在哪兒?它早就因為使用者放棄它的平臺而死了。)
同樣命運的還有 Perl。Perl 曾經非常流行。但 Perl 3 問世時,它與很多 Perl 2 程式碼都不相容。社群對此意見很大,只有好的程式碼得到了移植,其他程式碼都被拋棄了。然後 Perl 4 出現時又發生了同樣的事情。Perl 5 出現時,很多人乾脆換到了其他更穩定的程式語言。現在,只有很少一部分人仍然在使用 Perl 來維護現有的 Perl 專案。已經沒有任何新專案使用 Perl 了。
同樣,Python 的每個版本的程式碼也都是不一樣的,社群也不得不維護舊的版本。因此就要不斷維護一大堆陳舊已失去活力的 Python 程式碼,因為沒人想把它們移植到新版本。據我所知,現在沒有人在 Python 2 上寫新程式碼,但現有的 Python 2 又不得不維護,因為人們不願意將它們移植到 Python 3。在 Python 的官方網站上,Python 2.7、3.5、3.6 和 3.7 的文件都在維護中,因為還有舊程式碼在使用這些版本,他們沒辦法放棄這些版本。Python 就像程式語言中的百足之蟲,死而不僵。
進群:960410445 即可獲取數十套PDF!
安裝
絕大多數軟體包都可以通過 apt、yum、rpm 或某種安裝方式獲得最新的程式碼。而 Python 則不一樣。你無法知道 apt-get install python 會給你裝什麼版本,很可能這個版本跟你的程式碼都不相容。
所以,必須選擇你所需版本的 Python 來安裝。我參與過的一個專案使用的是 Python,但必須使用 Python 3.5(當時的最新版本)。最後我的電腦上安裝了 Python 2、 Python 2.6、Python 3 和 Python 3.5。兩個是作業系統自帶的,一個是為專案安裝的,另一個來自我安裝的某個不相關的軟體。雖然它們都是 Python,但並不是完全一樣。
要想給 Python 安裝軟體包,應該使用“pip”命令。(pip 的意思是“PIP Install Packages”,因為有人覺得遞迴的縮寫很有意思。)但由於系統上有多個版本的 Python,你必須要注意使用正確版本的 pip。否則,pip 可能執行的是 pip 2 而不是你需要的“pip 3.7”。(如果 pip 3.7 這個命令不存在,你還得指定正確的路徑。)
有個團隊成員建議我,我應該配置自己的環境,這樣一切都能使用 Python 3.5 的版本。這種方法很好,但後來我的另一個專案需要 Python 3.6 就出現麻煩了。兩個並行的專案使用了不同版本的 Python……嗯,這還不夠迷惑。(表示諷刺的表情是什麼來著?)
pip 安裝程式會把檔案放到使用者的本地目錄中。系統範圍上的軟體包不能使用 pip 安裝。而且你也不能使用 sudo pip,因為那會搞亂你整個電腦!因為使用 sudo 會在整個系統級別安裝軟體,一些軟體會安裝到錯誤的 Python 版本,一些會留在你的主目錄中但卻屬於 root,導致以後的非 sudo pip 命令由於許可權問題而出錯。所以不要使用 sudo pip。
另外,誰負責維護這些 pip 模組?是社群。也就是說,沒有固定的擁有者,也沒有強制的保證或審計。今年早些時候,某個版本的 PyPI 被發現有個後門,會盜取 SSH 密碼。這種事情根本不奇怪。(同樣的原因我也不用 Node.js 和 npm,我不相信他們的社群軟體倉庫。)
語法
我極其推崇程式碼的可讀性。初看起來,Python 程式碼似乎可讀性很高。沒錯,不過條件是你不要用它來開發大型程式碼。
絕大多數程式語言都有某種標識來表明作用域——即函式何時開始何時結束,動作包含在一個條件語句中,變數定義的範圍,等等。不論是 C、Java、JavaScript、Perl 還是 PIP,大家都使用{ ... } 為複雜的程式碼定義作用域,而 Lisp 使用(...)定義作用域。Python 呢?Python 使用空格。如果需要為一段複雜的程式碼定義作用域,就必須要縮排接下來的幾行。縮排結束就表明作用域的結束。
我第一次看到 Python 程式碼時,我認為使用縮排定義作用域是個不錯的想法。但是,這種方式有個巨大的缺點。這種方式可以寫出很深的巢狀,但程式碼行也會變得很寬,導致在文字編輯器中折行。長的函式和長的條件動作很難找出作用域的開始和結束。而且,只要你數錯了空格,或者在某行開頭放了三個空格而不是四個空格,那你需要花上幾個小時的除錯才能找到問題所在。
在其他語言中書寫除錯程式碼時,我習慣不放任何縮排。這樣我就能迅速瀏覽到程式碼,並在除錯結束之後很容易地找到除錯程式碼並刪掉。但用 Python 呢?任何縮排不正確的行都會導致縮排錯誤。也就是說,除錯程式碼必須混合到正式程式碼中。
包含
大多數程式語言都有一些方法可以包含其他程式碼塊。C 語言有“#include”。PHP有'include','include_once','require'和'require_once'。而 Python 有“import”。
Python 的匯入允許包括整個模組,模組的一部分或模組中的特定功能。想知道哪些東西可以匯入,並沒有什麼直觀的辦法。使用 C,你可以檢視 /usr/include/*.h。但是用 Python?最好使用 'python -v' 列出它看起來的所有位置,然後搜尋該列表中每個目錄和子目錄中的每個檔案。我曾經看到我喜歡 Python 的朋友 grep 標準模組來尋找他們想要匯入的東西。這是真事。
匯入功能還允許使用者重新命名匯入的程式碼。它們基本上定義了一個自定義名稱空間。乍一看,這似乎很不錯,但最終會影響可讀性和長期支援。重新命名模組非常適合小指令碼,但對於長程式來說真的很糟糕。使用 1-2 字母名稱空間的人,例如“import numpy as n”應該拖出去槍斃(或強制將其所有程式碼轉換為 Perl 5)。
但這不是最糟糕的部分。對於大多數語言,include 一段程式碼只會包含程式碼。一些語言(如面向物件的 C ++)會在存在全域性建構函式的情況下執行程式碼。類似地,一些 PHP 程式碼可能會定義全域性變數,因此匯入可以執行程式碼——但通常人們認為這不是一種好做法。相比之下,許多 Python 模組包括在匯入期間執行的初始化函式。你不知道哪部分程式碼在執行,你不知道它在做什麼,你甚至可能都注意不到。哦,有一種情況你會注意到——那就是出現名稱空間衝突的時候,在這種情況下,你需要花很多時間來追蹤原因。
命名法
在其他所有語言中,陣列都稱為“array”。在 Python 中,它們被稱為“list”。關聯陣列有時稱為'hash'(Perl),但 Python 稱之為'dict'。 Python 似乎沒有使用在計算機和資訊科學領域的常用術語。
然後是庫的名稱。 PyPy,PyPi,NumPy,SciPy,SymPy,PyGtk,Pyglet,PyGame ...(是的,第一個和第二個的讀音相同,但是它們是完全不同的東西。)我知道'py'表示 Python。但 py 放在開頭還是結尾能不能有個固定的寫法呢?
一些常見的庫只是放棄了類似雙關語的“Py”命名約定。這包括 matplotlib,nose,Pillow 和 SQLAlchemy。雖然一些名稱可能暗示庫的目的(例如,“SQLAlchemy”包含 SQL,所以它可能是一個 SQL 介面),但其他名稱只是隨機的單詞。如果你不知道“BeautifulSoup”是幹什麼的,你能從名稱中看出它是一個 HTML / XML 解析器嗎? (順便說一句,BeautifulSoup 有很好的文件和易於使用。如果每個 Python 模組都是這樣的,我不會抱怨太多。不幸的是,這並不是常態。大多數 Python 庫的文件寫得都很差。 )
總的來說,我將 Python 視為具有可怕且不一致的命名約定的庫的集合。我有一個常見的抱怨,開源專案通常有可怕的名字。除非你知道這個專案,否則你永遠不知道它的名字是什麼。除非你知道要尋找什麼,否則只能期待於偶然遇到某個別人提起的庫了。而且大多數 Python 庫都強化了這種負面的批評。
怪癖
每種語言都有它的怪癖。C 語言使用&和*來訪問地址空間和值的做法很奇怪。 C 還有使用 ++ 和 -- 來表示遞增/遞減的快捷方式。在 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 預設使用引用方式傳遞引數。這意味著更改引數可能會導致原始值的改變。
這是程序式程式設計、函數語言程式設計和麵向物件程式語言之間的重大差異之一。如果每個變數都是通過物件引用傳遞的,並且對變數的任何更改都會影響到任何使用該變數的地方,實際上就相當於一切都使用了全域性變數。針對一個變數使用不同的名稱實際上都是同一個物件,所以跟使用全域性變數沒什麼區別。C 程式設計師很久以前就知道,全域性變數是邪惡的,不應該被使用。
Python 中要想按值傳遞變數就必須使用額外的方式。簡單地寫下“a = b”只會給同一個物件起另一個名字,而不會將 b 的值複製到 a 中。如果你確實要複製該值,則需要使用複製功能。通常是用“a = b.copy()”。但是請注意我說的是“通常”。並非所有資料型別都支援“複製”的原型,還有可能複製功能不完整。在這些情況下,你必須使用一個名為“copy”的獨立庫,即“a = copy.deepcopy(b)”。
區域性命名
根據使用的庫或函式對程式進行命名,是常見的程式設計技巧。例如,如果我要對使用某個名為"libscreencapture.so”的庫的截圖程式進行測試,我會把我的程式叫做“screencapture.c",並編譯成“screencapture.exe”。
這個技巧能在 C、Java、JavaScript、Perl、PHP 等語言中正確使用,因為語言能區分出資源庫檔案和本地的程式,因為它們的路徑是不一樣的。但在 Python 卻不行。為什麼?Python 認為你想優先匯入本地檔案。如果我有個程式叫做“screencapture.py”,它要執行“import screencapture”,那麼它將匯入自己,而不是匯入系統庫。至少,你得把本地的庫命名為“myscreencapture.py”之類的才行。
也並非一無是處
Python 是個非常流行的語言,有很多擁護者,我甚至有一堆朋友真的很喜歡 Python。多年以來,我一直與他們討論這些問題,每次他們都表示同意。他們同意這些都是 Python 的問題,只是他們覺得這些還不足以讓他們失去對 Python 的興趣。
我的朋友經常提起 Python 那些非常酷的函式庫。我也同意,某些函式庫非常有用。例如, BeautifulSoup 就是我用過的最好的 HTML 解析器之一,NumPy 簡化了多維陣列和複雜的數學計算,TensorFlow 在機器學習方面非常有用。但我不會只因為 TensorFlow 或者 SciPy 就用 Python 編寫一個大而全的程式。我不會放棄可讀性和可維護性,這樣做不值得。
通常,我在批評某個東西時也會寫一些正面的東西。比如,我的部落格上“開源很糟糕”後面寫了一篇“開源很不錯”,批評完 ffmpeg 的侷限性之後也會特別提到它是最優秀的視訊處理庫。但 Python 我寫不出優點列表來,因為我真的覺得它太糟了。