在剛剛過去的 2018 年里,要說最熱門的科技領(lǐng)域是哪一個?毋庸置疑的是,人工智能必排在前列;而要論編程語言界,最流行的編程語言是誰?那非 Python 莫屬。2018 年 8 月,根據(jù)一年一度的IEEE Spectrum 編程語言來看,Python 一路高歌猛進(jìn),位居 48 種編程語言之首。不僅如此,在本月最新的 TIOBE 排行榜中,Python 再次超越 C++,位居排行榜前三甲,其受歡迎程度不言而喻,但就在此時,Python 卻慘遭開發(fā)者嫌棄了,而這究竟是怎么一回事?
以下為譯文:
有時候我會跟一些搞技術(shù)的朋友們出去吃飯,當(dāng)然我們會愉快地討論技術(shù)。我們討論自己項(xiàng)目、討論新鮮事、討論各種技術(shù)問題。最終不可避免地會討論到編程語言。有人就會說“我又得改 Java 代碼了。我恨 Java。”(抱歉了 Kyle……)(其實(shí)十多年前我們就給 Kyle 起了個綽號“Java 小子”。)另一個人就會談起某個古老的無所不包的 Shell 代碼,那段代碼無人敢碰。
至于我,嗯……我的話一出口眾人都驚呆了:我討厭 Python。我討厭它是有原因的。如果我能用 C 重寫某段 Python 代碼,那我一定會重寫的。
但我吼完之后,Bill 幽默地補(bǔ)刀:“那你內(nèi)心里是怎么看 Python 的,Neal?”所以就有了這篇文章。謹(jǐn)以此文獻(xiàn)給 Bill。
下面是我列出的“8 個理由說明 Python 很糟糕”。
版本
默認(rèn)的 Linux 安裝很可能會帶有多個版本的 Python。很可能會同時擁有 Python 2 和 Python 3,而且很可能同時擁有不同的子版本,如 3.5 和 3.7。理由是:Python 3 不能與 Python 2 完全兼容。即使一些子版本號也會造成無法后向兼容。
我不反對給語言添加新功能,甚至退役一些舊版本也無所謂。但是,不同的軟件需要不同的 Python。我給 Python 3.5 編寫的代碼不能在 Python 3.7 上正常運(yùn)行,除非我專門將其移植到 3.7。許多 Linux 開發(fā)者都認(rèn)為移植不值得,所以 Ubuntu 就同時帶有 Python 2 和 Python 3,因?yàn)椴煌暮诵墓δ苄枰煌?Python 版本。
缺乏向后兼容和版本之間的割裂通常是死亡的信號。Commodore 是世界上最早制造家用電腦的廠商之一(遠(yuǎn)在 IBM PC 和蘋果之前)。但 Commodore 的 PET 沒法與后繼的 Commodore CBM 電腦兼容。而且,CBM 也不兼容 VIC-20、Commodore-64、Amiga 等。所以,你只能花大把時間把代碼從一個平臺移植到另一個平臺,否則就要完全放棄那個平臺。(現(xiàn)在 Commodore 在哪兒?它早就因?yàn)橛脩舴艞壦钠脚_而死了。)
同樣命運(yùn)的還有 Perl。Perl 曾經(jīng)非常流行。但 Perl 3 問世時,它與很多 Perl 2 代碼都不兼容。社區(qū)對此意見很大,只有好的代碼得到了移植,其他代碼都被拋棄了。然后 Perl 4 出現(xiàn)時又發(fā)生了同樣的事情。Perl 5 出現(xiàn)時,很多人干脆換到了其他更穩(wěn)定的編程語言。現(xiàn)在,只有很少一部分人仍然在使用 Perl 來維護(hù)現(xiàn)有的 Perl 項(xiàng)目。已經(jīng)沒有任何新項(xiàng)目使用 Perl 了。
同樣,Python 的每個版本的代碼也都是不一樣的,社區(qū)也不得不維護(hù)舊的版本。因此就要不斷維護(hù)一大堆陳舊已失去活力的 Python 代碼,因?yàn)闆]人想把它們移植到新版本。據(jù)我所知,現(xiàn)在沒有人在 Python 2 上寫新代碼,但現(xiàn)有的 Python 2 又不得不維護(hù),因?yàn)槿藗儾辉敢鈱⑺鼈円浦驳?Python 3。在 Python 的官方網(wǎng)站上,Python 2.7、3.5、3.6 和 3.7 的文檔都在維護(hù)中,因?yàn)檫€有舊代碼在使用這些版本,他們沒辦法放棄這些版本。Python 就像編程語言中的百足之蟲,死而不僵。
安裝
絕大多數(shù)軟件包都可以通過 apt、yum、rpm 或某種安裝方式獲得最新的代碼。而 Python 則不一樣。你無法知道 apt-get install python 會給你裝什么版本,很可能這個版本跟你的代碼都不兼容。
所以,必須選擇你所需版本的 Python 來安裝。我參與過的一個項(xiàng)目使用的是 Python,但必須使用 Python 3.5(當(dāng)時的最新版本)。最后我的電腦上安裝了 Python 2、 Python 2.6、Python 3 和 Python 3.5。兩個是操作系統(tǒng)自帶的,一個是為項(xiàng)目安裝的,另一個來自我安裝的某個不相關(guān)的軟件。雖然它們都是 Python,但并不是完全一樣。
要想給 Python 安裝軟件包,應(yīng)該使用“pip”命令。(pip 的意思是“PIP Install Packages”,因?yàn)橛腥擞X得遞歸的縮寫很有意思。)但由于系統(tǒng)上有多個版本的 Python,你必須要注意使用正確版本的 pip。否則,pip 可能運(yùn)行的是 pip 2 而不是你需要的“pip 3.7”。(如果 pip 3.7 這個命令不存在,你還得指定正確的路徑。)
有個團(tuán)隊(duì)成員建議我,我應(yīng)該配置自己的環(huán)境,這樣一切都能使用 Python 3.5 的版本。這種方法很好,但后來我的另一個項(xiàng)目需要 Python 3.6 就出現(xiàn)麻煩了。兩個并行的項(xiàng)目使用了不同版本的 Python……嗯,這還不夠迷惑。(表示諷刺的表情是什么來著?)
pip 安裝程序會把文件放到用戶的本地目錄中。系統(tǒng)范圍上的軟件包不能使用 pip 安裝。而且你也不能使用 sudo pip,因?yàn)槟菚銇y你整個電腦!因?yàn)槭褂?sudo 會在整個系統(tǒng)級別安裝軟件,一些軟件會安裝到錯誤的 Python 版本,一些會留在你的主目錄中但卻屬于 root,導(dǎo)致以后的非 sudo pip 命令由于權(quán)限問題而出錯。所以不要使用 sudo pip。
另外,誰負(fù)責(zé)維護(hù)這些 pip 模塊?是社區(qū)。也就是說,沒有固定的擁有者,也沒有強(qiáng)制的保證或?qū)徲?jì)。今年早些時候,某個版本的 PyPI 被發(fā)現(xiàn)有個后門,會盜取 SSH 密碼。這種事情根本不奇怪。(同樣的原因我也不用 Node.js 和 npm,我不相信他們的社區(qū)軟件倉庫。)
語法
我極其推崇代碼的可讀性。初看起來,Python 代碼似乎可讀性很高。沒錯,不過條件是你不要用它來開發(fā)大型代碼。
絕大多數(shù)編程語言都有某種標(biāo)識來表明作用域——即函數(shù)何時開始何時結(jié)束,動作包含在一個條件語句中,變量定義的范圍,等等。不論是 C、Java、JavaScript、Perl 還是 PIP,大家都使用{ ... } 為復(fù)雜的代碼定義作用域,而 Lisp 使用(...)定義作用域。Python 呢?Python 使用空格。如果需要為一段復(fù)雜的代碼定義作用域,就必須要縮進(jìn)接下來的幾行。縮進(jìn)結(jié)束就表明作用域的結(jié)束。
我第一次看到 Python 代碼時,我認(rèn)為使用縮進(jìn)定義作用域是個不錯的想法。但是,這種方式有個巨大的缺點(diǎn)。這種方式可以寫出很深的嵌套,但代碼行也會變得很寬,導(dǎo)致在文本編輯器中折行。長的函數(shù)和長的條件動作很難找出作用域的開始和結(jié)束。而且,只要你數(shù)錯了空格,或者在某行開頭放了三個空格而不是四個空格,那你需要花上幾個小時的調(diào)試才能找到問題所在。
在其他語言中書寫調(diào)試代碼時,我習(xí)慣不放任何縮進(jìn)。這樣我就能迅速瀏覽到代碼,并在調(diào)試結(jié)束之后很容易地找到調(diào)試代碼并刪掉。但用 Python 呢?任何縮進(jìn)不正確的行都會導(dǎo)致縮進(jìn)錯誤。也就是說,調(diào)試代碼必須混合到正式代碼中。
包含
大多數(shù)編程語言都有一些方法可以包含其他代碼塊。C 語言有“#include”。PHP有'include','include_once','require'和'require_once'。而 Python 有“import”。
Python 的導(dǎo)入允許包括整個模塊,模塊的一部分或模塊中的特定功能。想知道哪些東西可以導(dǎo)入,并沒有什么直觀的辦法。使用 C,你可以查看 /usr/include/*.h。但是用 Python?最好使用 'python -v' 列出它看起來的所有位置,然后搜索該列表中每個目錄和子目錄中的每個文件。我曾經(jīng)看到我喜歡 Python 的朋友 grep 標(biāo)準(zhǔn)模塊來尋找他們想要導(dǎo)入的東西。這是真事。
導(dǎo)入功能還允許用戶重命名導(dǎo)入的代碼。它們基本上定義了一個自定義命名空間。乍一看,這似乎很不錯,但最終會影響可讀性和長期支持。重命名模塊非常適合小腳本,但對于長程序來說真的很糟糕。使用 1-2 字母命名空間的人,例如“import numpy as n”應(yīng)該拖出去槍斃(或強(qiáng)制將其所有代碼轉(zhuǎn)換為 Perl 5)。
但這不是最糟糕的部分。對于大多數(shù)語言,include 一段代碼只會包含代碼。一些語言(如面向?qū)ο蟮?C ++)會在存在全局構(gòu)造函數(shù)的情況下執(zhí)行代碼。類似地,一些 PHP 代碼可能會定義全局變量,因此導(dǎo)入可以運(yùn)行代碼——但通常人們認(rèn)為這不是一種好做法。相比之下,許多 Python 模塊包括在導(dǎo)入期間運(yùn)行的初始化函數(shù)。你不知道哪部分代碼在運(yùn)行,你不知道它在做什么,你甚至可能都注意不到。哦,有一種情況你會注意到——那就是出現(xiàn)命名空間沖突的時候,在這種情況下,你需要花很多時間來追蹤原因。
命名法
在其他所有語言中,數(shù)組都稱為“array”。在 Python 中,它們被稱為“l(fā)ist”。關(guān)聯(lián)數(shù)組有時稱為'hash'(Perl),但 Python 稱之為'dict'。 Python 似乎沒有使用在計(jì)算機(jī)和信息科學(xué)領(lǐng)域的常用術(shù)語。
然后是庫的名稱。 PyPy,PyPi,NumPy,SciPy,SymPy,PyGtk,Pyglet,PyGame ...(是的,第一個和第二個的讀音相同,但是它們是完全不同的東西。)我知道'py'表示 Python。但 py 放在開頭還是結(jié)尾能不能有個固定的寫法呢?
一些常見的庫只是放棄了類似雙關(guān)語的“Py”命名約定。這包括 matplotlib,nose,Pillow 和 SQLAlchemy。雖然一些名稱可能暗示庫的目的(例如,“SQLAlchemy”包含 SQL,所以它可能是一個 SQL 接口),但其他名稱只是隨機(jī)的單詞。如果你不知道“BeautifulSoup”是干什么的,你能從名稱中看出它是一個 HTML / XML 解析器嗎? (順便說一句,BeautifulSoup 有很好的文檔和易于使用。如果每個 Python 模塊都是這樣的,我不會抱怨太多。不幸的是,這并不是常態(tài)。大多數(shù) Python 庫的文檔寫得都很差。 )
總的來說,我將 Python 視為具有可怕且不一致的命名約定的庫的集合。我有一個常見的抱怨,開源項(xiàng)目通常有可怕的名字。除非你知道這個項(xiàng)目,否則你永遠(yuǎn)不知道它的名字是什么。除非你知道要尋找什么,否則只能期待于偶然遇到某個別人提起的庫了。而且大多數(shù) Python 庫都強(qiáng)化了這種負(fù)面的批評。
怪癖
每種語言都有它的怪癖。C 語言使用&和*來訪問地址空間和值的做法很奇怪。 C 還有使用 ++ 和 -- 來表示遞增/遞減的快捷方式。在 Bash 中,當(dāng)引用括號和正則表達(dá)式的句點(diǎn)等特殊字符時,就會出現(xiàn)“何時使用反斜杠”的問題。 JavaScript 存在兼容性問題(并非每個瀏覽器都支持所有有用的功能)。然而,Python 比我見過的任何其他語言都有更多怪癖。就拿字符串來說:
C 語言中雙引號表示字符串,單引號表示字符。
PHP 和 Bash 中,任何引號都可以用來表示字符串。但是,雙引號可以在字符串中嵌入變量。與此相反,單引號字符串是單純的字符串,任何類似嵌入式變量的名稱都不會擴(kuò)展。
在 JavaScript 中,單引號和雙引號之間沒有任何區(qū)別。
Python 中的單引號和雙引號之間也沒有區(qū)別。但是,如果您希望字符串跨越行,則需要使用三引號"""string"""或“''string'''。如果你想使用二進(jìn)制文件,那么你需要用 b(b'binary')或 r(r'raw')來指示字符串的形式。有時你需要使用 str(string) 將字符串轉(zhuǎn)換為字符串,或使用 string.encode('utf-8') 將其轉(zhuǎn)換為 utf8。
如果你認(rèn)為 =,== 和 === 在 PHP 和 JavaScript 中有點(diǎn)奇怪,那你應(yīng)該看看 Python 中的引號使用方法再下結(jié)論。
對象的引用傳遞
大多數(shù)編程語言都用值方式傳遞函數(shù)參數(shù)。如果函數(shù)改變了值,則改變不會影響到調(diào)用的代碼。但正如我已經(jīng)解釋過的那樣,Python 在這方面依然與眾不同。 Python 默認(rèn)使用引用方式傳遞參數(shù)。這意味著更改參數(shù)可能會導(dǎo)致原始值的改變。
這是過程式編程、函數(shù)式編程和面向?qū)ο缶幊陶Z言之間的重大差異之一。如果每個變量都是通過對象引用傳遞的,并且對變量的任何更改都會影響到任何使用該變量的地方,實(shí)際上就相當(dāng)于一切都使用了全局變量。針對一個變量使用不同的名稱實(shí)際上都是同一個對象,所以跟使用全局變量沒什么區(qū)別。C 程序員很久以前就知道,全局變量是邪惡的,不應(yīng)該被使用。
Python 中要想按值傳遞變量就必須使用額外的方式。簡單地寫下“a = b”只會給同一個對象起另一個名字,而不會將 b 的值復(fù)制到 a 中。如果你確實(shí)要復(fù)制該值,則需要使用復(fù)制功能。通常是用“a = b.copy()”。但是請注意我說的是“通常”。并非所有數(shù)據(jù)類型都支持“復(fù)制”的原型,還有可能復(fù)制功能不完整。在這些情況下,你必須使用一個名為“copy”的獨(dú)立庫,即“a = copy.deepcopy(b)”。
局部命名
根據(jù)使用的庫或函數(shù)對程序進(jìn)行命名,是常見的編程技巧。例如,如果我要對使用某個名為"libscreencapture.so”的庫的截屏程序進(jìn)行測試,我會把我的程序叫做“screencapture.c",并編譯成“screencapture.exe”。
gcc-oscreencapture.exescreencapture.c-lscreencapture
這個技巧能在 C、Java、JavaScript、Perl、PHP 等語言中正確使用,因?yàn)檎Z言能區(qū)分出資源庫文件和本地的程序,因?yàn)樗鼈兊穆窂绞遣灰粯拥摹5?Python 卻不行。為什么?Python 認(rèn)為你想優(yōu)先導(dǎo)入本地文件。如果我有個程序叫做“screencapture.py”,它要執(zhí)行“import screencapture”,那么它將導(dǎo)入自己,而不是導(dǎo)入系統(tǒng)庫。至少,你得把本地的庫命名為“myscreencapture.py”之類的才行。
也并非一無是處
Python 是個非常流行的語言,有很多擁護(hù)者,我甚至有一堆朋友真的很喜歡 Python。多年以來,我一直與他們討論這些問題,每次他們都表示同意。他們同意這些都是 Python 的問題,只是他們覺得這些還不足以讓他們失去對 Python 的興趣。
我的朋友經(jīng)常提起 Python 那些非常酷的函數(shù)庫。我也同意,某些函數(shù)庫非常有用。例如, BeautifulSoup 就是我用過的最好的 HTML 解析器之一,NumPy 簡化了多維數(shù)組和復(fù)雜的數(shù)學(xué)計(jì)算,TensorFlow 在機(jī)器學(xué)習(xí)方面非常有用。但我不會只因?yàn)?TensorFlow 或者 SciPy 就用 Python 編寫一個大而全的程序。我不會放棄可讀性和可維護(hù)性,這樣做不值得。
通常,我在批評某個東西時也會寫一些正面的東西。比如,我的博客上“開源很糟糕”后面寫了一篇“開源很不錯”,批評完 ffmpeg 的局限性之后也會特別提到它是最優(yōu)秀的視頻處理庫。但 Python 我寫不出優(yōu)點(diǎn)列表來,因?yàn)槲艺娴挠X得它太糟了。
-
編程語言
+關(guān)注
關(guān)注
10文章
1947瀏覽量
34832 -
python
+關(guān)注
關(guān)注
56文章
4800瀏覽量
84835
原文標(biāo)題:頻頻霸榜的Python,竟遭開發(fā)者嫌棄!
文章出處:【微信號:rgznai100,微信公眾號:rgznai100】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論