查看原文
其他

频频霸榜的 Python,竟遭开发者嫌弃!

Neal Krawetz CSDN 2019-02-15

在刚刚过去的 2018 年里,要说最热门的科技领域是哪一个?毋庸置疑的是,人工智能必排在前列;而要论编程语言界,最流行的编程语言是谁?那非 Python 莫属。2018 年 8 月,根据一年一度的 IEEE Spectrum 编程语言来看,Python 一路高歌猛进,位居 48 种编程语言之首。不仅如此,在本月最新的 TIOBE 排行榜中,Python 再次超越 C++,位居排行榜前三甲,其受欢迎程度不言而喻,但就在此时,Python 却惨遭开发者嫌弃了,而这究竟是怎么一回事?

作者 | Neal Krawetz
译者 | 弯月
责编 | 屠敏
出品 | CSDN(ID:CSDNnews)

以下为译文:

有时候我会跟一些搞技术的朋友们出去吃饭,当然我们会愉快地讨论技术。我们讨论自己项目、讨论新鲜事、讨论各种技术问题。最终不可避免地会讨论到编程语言。有人就会说“我又得改 Java 代码了。我恨 Java。”(抱歉了 Kyle……)(其实十多年前我们就给 Kyle 起了个绰号“Java 小子”。)另一个人就会谈起某个古老的无所不包的 Shell 代码,那段代码无人敢碰。

至于我,嗯……我的话一出口众人都惊呆了:我讨厌 Python。我讨厌它是有原因的。如果我能用 C 重写某段 Python 代码,那我一定会重写的。

但我吼完之后,Bill 幽默地补刀:“那你内心里是怎么看 Python 的,Neal?”所以就有了这篇文章。谨以此文献给 Bill。

下面是我列出的“8 个理由说明 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 就像编程语言中的百足之虫,死而不僵。


安装


绝大多数软件包都可以通过 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”。

gcc -o screencapture.exe screencapture.c -lscreencapture

这个技巧能在 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 我写不出优点列表来,因为我真的觉得它太糟了。

原文:https://www.hackerfactor.com/blog/index.php?/archives/825-8-Reasons-Python-Sucks.html

作者:Neal Krawetz,在 Texas A&M University 获得计算机科学的博士学位,创立了 HackerFactor Solutions,专注于非传统的计算机法医学、在线评测、网络和计算机安全等。

本文为 CSDN 翻译,如需转载,请注明来源出处。

 热 文 推 荐 

☞ 华为很好,但我不愿再回去

☞ 背靠 Google 的 Go 语言,就不会失败?

☞ 你是否真的懂数组?

☞ 无业务不技术:那些誓用区块链重塑的行业,发展怎么样了?

☞ 下一次 IT 变革:边缘计算(Edge computing)

☞ 12306 脱库 410 万用户数据究竟从何泄漏?

☞ 年度重磅:《AI聚变:2018年优秀AI应用案例TOP 20》正式发布

☞ 老程序员肺腑忠告:千万别一辈子靠技术生存!

print_r('点个好看吧!');
var_dump('点个好看吧!');
NSLog(@"点个好看吧!");
System.out.println("点个好看吧!");
console.log("点个好看吧!");
print("点个好看吧!");
printf("点个好看吧! ");
cout << "点个好看吧!" << endl;
Console.WriteLine("点个好看吧!");
fmt.Println("点个好看吧!");
Response.Write("点个好看吧!");
alert("点个好看吧!")
echo "点个好看吧!"

点击“阅读原文”,打开 CSDN App 阅读更贴心!

喜欢就点击“好看”吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存