54

为什么我觉得Python烂的要死?原因有八

 5 years ago
source link: https://36kr.com/p/5167995.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

编者按:本文来自“ 新智元 ”(ID:AI_era),作者:元子、大明、木青。36氪经授权转载。

作为机器学习程序员的首选编程语言,Python近年来可谓如日中天,人气连年暴涨。由于AI热潮持续不断,Python在今年更是取代Java,成为世界范围内最受大学生欢迎的编程语言。很多机器学习领域的教材、文章和技术文档,给出代码时会以Python作为示例语言,可见Python受欢迎程度之高。

Python具有上手快、门槛低、语法结构相对简单等优点,初学者易入门、老手的二次学习成本也低,再加上机器学习任务上优势独具,受热捧简直是水到渠成的事。

但凡事有例外,东西再好也不可能人人都爱。

近日在hackerfactor上,一位名叫Neal Krawetz的人就撰文,指出了自己无法忍受Python的八大原因,把Python里里外外吐槽了一遍。

文章列出了作者认为Python存在重大缺陷的八条理由,包括版本兼容性问题、安装版本混乱、在程序关键字命名规则、常用库命名规则上独树一帜,且缺乏一致性、赋值传递混乱、本地文件命名策略易出错等。

总之一通下来,把Python贬得够呛。这篇文章在当下Python大热的背景下可算是一朵“奇葩“了。看多了Python赞歌是不是有点审美疲劳了?不妨换换口味。

原因1:版本之间不兼容

安装Linux后,那么它很可能默认会安装多个版本的Python,可能有Python2和Python3,甚至更多零零碎碎的版本,如3.5或3.7。

这是有原因的:Python3与Python2不完全兼容,一些其他版本在这方面的缺陷也足够明显——向后兼容性不足(backwards compatibility,也称为向下兼容性)。

所以Ubuntu同时安装了Python2和Python3,因为这些版本的核心功能是不同的。

r6VJN3y.jpg!web

缺乏向后兼容和分离版本通常是走向衰败的预警。Commodore创建了第一台家用电脑(要远早于IBM PC或Apple之前)。但Commodore PET与随后的Commodore CBM计算机并不兼容,而CBM与VIC-20,Commodore-64,Amiga等也不兼容。因此,用户要么花费大量时间将代码从一个平台导到另一个平台,要么就直接放弃了这个平台——Commodore就是前车之鉴。当用户选择放弃平台时,它就注定会消失。

同样,Perl曾经很受欢迎。但是当Perl3问世时,它并没有完全向后兼容Perl2的代码。接下来是Perl4。当Perl5问世时,很多人选择转向使用其他更稳定的编程语言。所以今天,只有一小部分人还在积极使用Perl来维护现有的Perl项目,而其他任何基于Perl的重大新项目再也没有出现过。

同样,Python为每个版本设计了不同的代码孤岛。社区一直拖拽着这些旧版本,所以你最终也只能得到那些旧的、过时的Python代码,因为没有人愿意花时间将它导到最新版本上。

据我所知,没有人在Python2上创建新的代码,但我们还让它苟延残喘着,因为没人将所需的代码导到Python3.x. 在官方Python网站上,这些文档被主动维护并可用于Python 2.7、3.5、3.6和3.7——因为他们无法放弃旧代码。Python就像编程语言的僵尸——行尸走肉般向前走。

原因2:安装太太太太麻烦了

通常来说,你直接apt、yum、rpm后得到就是最新稳定版。

但你如果'apt-get install python',就不知道是什么版本,可能与你需要的所有代码都不兼容。所以你在安装的时候需要指定Python版本。

有一个项目需要用Python3.5(当时最新的版本),然而我的电脑上最终安装了一大堆版本:Python2、Python2.6、Python3和Python3.5。两个来自操作系统,一个为了项目安装,一个是因为我安装了一些不相关的软件。

尽管它们都是“Python”,但它们并非完全相同。

如果你想安装Python的软件包,你应该使用“pip”(Pip代表“Pip Installs Packages”)。但是由于系统上有许多版本的Python,你必须记住使用正确版本的pip。否则,'pip'可能会运行'pip2'而不是你需要的'pip3.7'。(如果名称不存在,你需要指定pip3.7的实际安装路径。)

我被一个朋友告知我需要配置环境,以便所有东西都能使用Python 3.5。这种方法的确很有效,但没有持续多久,因为我开始了另一个需要Python 3.6版本的项目。两个并发项目有两个不同版本的Python——emmmm,这有点一言难尽吧。

pip安装程序将文件放在用户的本地目录中。你不能使用pip来安装系统范围的库,并且Gawd会阻止你犯下运行'sudo pip'的错误,因为这会搞砸整个电脑!

顺便说一句,是谁维护这些pip模块?答案是社区。也就是说,没有明确的所有者,也没有强制性的责任所属。今年早些时候,一个版本的PyPi有一个后门发生了SSH凭据盗窃,但我对此一点都不惊讶,因为社区存储库根本不值得相信。出于同样的原因,我也不使用Node.js和npm。

原因3:令人头疼的语法问题,作用域使用空格导致可读性差

我是可读代码的坚定信徒。乍一看,Python似乎非常易读,而当你开始制作大型代码库,这种易读性就会减弱了。

其他编程语言,像C, Java, JavaScript, Perl, and PHP,用{} 来表示作用域;List用()。Python用空格。如果你需要给一个复杂的代码定义一个作用域,然后你缩进了下面几行代码,当缩进终止后,作用域就终止了。

Python手册说你可以使用任意数量的空格或制表符来定义范围。但是,每次缩进都要用四个空格!如果要缩进两次以进行嵌套,那就得使用八个空格!

Python社区已经对这个术语进行标准化,尽管它没出现在Python手册中。文档中的示例说可以使用TAB、“TAB+1空格”等等。但是社区却对4个空格有着丧心病狂的偏执!因此,除非你打算永远不向其他任何人展示你写的代码,否则每个缩进都要使用四个空格。

当我第一次看到Python代码时,我认为使用缩进来定义范围似乎是个好主意。事实上,我太天真了,这简直是一个天大的缺点。

深度嵌套是可以进行的,但每行代码会变得很宽,不得不在文本编辑器中换行。长函数和长条件操作都可能让开始与结束变得难以匹配。我可怜那些错误计算空格数量(比如只输了3个空格而不是四个)的人,因为这样的错误需要数小时进行调试和追踪。

我debug代码习惯没有缩进,这样我就可以快速浏览代码,并在完成后轻松识别和删除debug代码。

但是用Python呢?缩进错误的话,都会报错。

原因4:特立独行的加载库方式

大多数编程语言都有一些方法可以包含其他代码块。对于C,它是“#include”。对于PHP,有'include','include_once','require'和'require_once'。而对于Python,则是“import”。

Python的import允许导入整个模块、模块的一部分或模块中的特定功能。但查找导入代码块的方法却很麻烦。使用C,直接看/usr/include/*.h就行了。但用Python?最好使用'python -v'列出所有位置,然后搜索该列表中每个目录和子目录中的每个文件。这真的很麻烦。

导入功能还允许用户重命名导入的代码,它们基本上定义了一个命名空间。乍一看,这似乎很不错,但这最终会影响可读性和长期支持。重命名模块非常适合小脚本,但对于大程序来说真的很糟糕。这样的操作“import numpy as n”,应该被打死。

但这不是最糟糕的部分。对于大多数语言,包含代码真的只意味着包含代码。而一些语言(如面向对象的C ++)则可以执行代码。类似地,一些PHP代码可能会定义全局变量,因此一项import可以运行代码,但这通常被认为是一种不好的做法。相比之下,许多Python模块包含在导入期间运行的初始化函数。你不知道什么在运行,你不知道它在做什么,你甚至都没察觉到。除非存在命名空间冲突,否则在这种情况下,你需要花很长时间来查找原因。

原因5:关键字和库命名“独树一帜”

在其他所有编程语言中,数组都称为“array”。在Python中,数组被称为“list”。在其他语言中,关联数组有时称为'hash'(Perl),但Python里叫做“dictionary”。 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库的命名上看,我现在更加确信这个观点了。

原因6:其他“独树一帜”之处略多

每种语言都有它的怪癖。在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中使用引号时再说吧。

原因7:赋值方式怪异

大多数编程语言都按值传递函数参数。如果函数改变了值,则结果不会传递回调用代码。但Python不一样。 Python默认使用pass-by-object-reference参数执行函数。这意味着更改源变量可能最终会改变值。

这是面向程序、面向函数和面向对象编程语言之间的重大差异之一。如果每个变量都是通过对象引用传递的,而且对变量的任何更改都会导致其他所有地方的变量值变化,那么其实也可以全部使用全局变量来处理所有内容。使用不同的名称调用同一个对象不会更改对象的值,因此实际上该对象就是全局的。C语言程序员有句老话,全局变量是邪恶的,不应该使用。

在Python中,必须按值传递变量。“a = b”只是为同一个对象空间指定另一个名称,并不会将b的值赋到a中。如果要赋值,则需要使用copy函数。通常格式是“a = b.copy()”。但是,请注意我说的是“通常”。并非所有数据类型都能够这样赋值,部分功能可能不完整。这时需要使用一个名为“copy”的独立库:“a = copy.deepcopy(b)”。

原因8:本地程序命名易混乱

根据使用的库或函数来命名程序是一种常见的编程技术。比如,我正在使用名为“libscreencapture.so”的C语言库测试屏幕捕获程序,我调用的程序可能会命名为“screencapture.c”,编译后命名为“screencapture.exe”。

如果使用C,Java,JavaScript,Perl,PHP等语言,这种命名方式很好用,因为程序语言可以很容易地将资源库与本地程序区分开来,因为彼此的路径是不同的。但是如果用的是Python,永远不要这样命名。

为什么? Python总是假定用户首先要导入本地代码。如果我有一个名为“screencapture.py”的程序使用“importscreencapture”,那么它将导入自己而不是系统库。至少,本地程序需要命名为“myscreencapture.py”才能避免这种错误。

当然了,吐槽了这么多,但其实Python并非一无是处。

Python是一种非常流行的语言,拥有数量庞大的使用者。我身边有一些朋友非常喜欢Python,这是他们首选的编程语言。多年来,我和他们讨论过这些问题,每次他们都点头表示同意。他们并不否认Python确实存在这些问题,只是觉得这些缺点不足以让他们抛弃Python。

我的朋友经常在编程中将所有存在的非常酷的Python库统统引用。我也认为一些库确实非常有用。例如,BeautifulSoup是我用过的最好的HTML解析器之一,NumPy使得多维数组和复杂的数学过程更容易实现,而TensorFlow则对于机器学习非常有用。但是,我不会因为喜欢TensorFlow或SciPy,而在Python中创建一个单片程序。为了某些库的便利性,放弃程序可读性和可维护性,属于得不偿失。

一般来说,我在写关于某个主题的负面批评文章时,也会尝试写一些正面的东西。比如当我写FFmpeg的局限性时,我也明确提到它是最好的视频处理库。但我这里写不出关于Python的什么优点了,因为我真的觉得Python很烂。

此文一发,在评论区引发了激烈的争论:

“是你不懂Python”

  1. Mario Abarca

  2. 你的这些问题可以总结为一点:你不喜欢Python因为它和C风格不一样

  3. 版本不兼容不是bug,是特性;我就觉得没人维护的东西就不应该再用了

  4. 用虚拟环境安装不同的python版本而不是安装在同一个环境下

  5. 现代编辑器默认TAB=4个空格。你也不需要非得用4个空格,但要确保一致性

  6. 官方的文档特别好,真的。要是标准库里没有,翻翻The Hitchhiker’s Guide to Python这本书

  7. 我觉得Python的命名风格特别好,更直观。list不是数组,就是序列;关联数组明明就是dict

  8. 二进制字符串前面加个b,是因为Unicode规范中,1字节≠1byte

  9. 这样做的好处是,我可以随时随地引用一个东西,而不需要每次都去复制粘贴原来的名字

  10. 同上

notacoward

  1. 1和2是同一个问题,有关整个生态,跟语言本身无关。因为这类社区维护的项目都是不同的人花费宝贵的业余时间去维护,每个人都有每个人的习惯和价值观

  2. 这个只能说你自己太个性了。我们大家保持默认的统一风格,对于别人维护起来明显更容易

  3. C/C++的include很难处理模块接口

  4. list和array不是一个东西。下一个

  5. 每个语言都有自己的一套转换方式。Python可能不是最完美的,但是其他的更差,呵呵

  6. 对象引用效率更高。尤其是当变量名不一致的时候,你直接复制会有问题。但是你引用一下,就好多了

  7. 最好不要把自己的程序命名成标准库里的程序或者模块的名字

folkrav

首先要纠正下你,PyPy和PyPi发音不一样。前一个是“派派”,后一个是“派-屁-爱”

其次,名称很重要吗?第三方诶大哥,啥名字都可以出现诶大哥。你就能保证你起名的时候,能做到信达雅吗大哥?

jaxtellerSoA

我就不明白了。用缩进来定义作用域,怎么就不好了?多一目了然啊!别的语言{}里面不也得缩进吗?再说了,你就不觉得按住shift才能打出{}很难受吗?

riskable

我跟C粉儿讨论过“缩进vs括号”这个问题。他说没有括号怎么能轻松找出作用域呢?

标准Py粉儿答案是:啊原来你们喜欢括号是因为你们的代码坏习惯啊。

我想了想,可能这么问更恰当:假如不使用文本编辑器/IDE来突出显示括号或它们之间的空间,你还是坚持用括号不用缩进吗?

我估计他终于get到我的点了,说:啊我明白了,你之所以用缩进是因为Python编辑器太烂了啊!真可怜。

Sign。

“我也不喜欢Python”

cutety

Python是我上手的第一个语言,但我以后再也不会用了。当然作者的这些问题,在我看来都不是问题,个人习惯而已。

  1. 包管理模式简直烂到家。那么多包管理器可以借鉴啊,可以让pip不那么烂啊

  2. 就不能有个标准包管理器有个标准manifest吗?又不会怀孕!

twunde

安装确实是个让人头疼的问题。是的很多人提到了安装虚拟环境,venv/virtualenv。Ruby有RVM,可以轻松的在同样环境下使用不同版本。我宁愿挨个给Ruby,PHP,Perl…做环境配置,也不愿意给Python配置。

nicoburns

哦!多行Lambda!我在JS里的最爱。Python里,没!有!了!

setpatchaddress

我从1.5就开始用Python了。缩进来表示语句块,是我最最最不能忍受的!

dbcurtis

我就喜欢C那种的括号,不喜欢Python的缩进方式。

colanderman

我觉得Python最大的问题其实是内部模型对于它的意图而言过于复杂了,就是一个有经验的开发者都很难理解,别说初学者了。

来源:hackerfactor;HackerNews


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK