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

python的功能到底有多强大(频频霸榜的Python竟遭开发者嫌弃)(1)

下面是来自网友统计列出的“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 比我见过的任何其他语言都有更多怪癖。就拿字符串来说:

如果你认为 =,== 和 === 在 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 的兴趣。大家觉得呢?

,