返回 登录
0

为什么抵制Python 3

阅读13325

这份文档列出了为什么初学者应该避免学Python 3的原因,在这里我给出两类原因:第一类是针对零基础的,另一类是对于有一定编程基础的人来说的。第一部分,将会从非技术的角度谈论,帮助初学者不受外部宣传和压力的影响做出合理的决定。第二部分,将讨论目前Python 3存在的缺陷,以及这些缺陷为什么会阻碍程序员的工作。

我不会教初学者Python 3,因为我不想让他们觉得自己“在编程方面糟透了”——而实际上,这并不是他们的错,而是Python 3的问题。对他们来说,这很不公平,所以我一般会教Python 2,最大限度地降低他们的阻碍。

最重要的原因

在讲技术层面的东西之前,我想讨论一下作为初学者不应该使用Python 3最根本的社会原因:

Python 3极有可能是一个巨大的失败,它将会杀了Python。

Python 3的接受度只有30%,可能不是很准确,不过总归不高。我认为,Python 3基本上算是“死了”,也不会走得更远了,原因在下面列出。这些原因都是易用性的问题,很容易就能修改,但是Python项目组采取了一种傲慢的态度,认为这是一种feature,是对用户好。这是一门语言死亡的信号,所以如果你计划将来使用Python 3,有可能陷入麻烦。

道理很简单。如果你学Python 2,你可以使用所有Python 2的库,除非这门语言死了,或者你有更高的需求。但是如果你学Python 3的话,未来就很不确定了。有很大的可能,到头来你还得从Python 2重新开始。

现在已经十年过去了,可能下一个十年过去,Python 3的接受程度还是30%。即使是在Python最擅长的领域——科学方面,接受程度也是只有25-30%。现在,是时候承认它的失败,采取新计划了。

初学者可以理解的原因

除了Python 3的不确定之外,还有很多原因。在这部分中,我将从非程序员也能理解的角度描述,如果你理解不了的话,也可以继续读下面技术方面的讨论。

没有考虑到你的兴趣

Python项目组极力劝说你使用Python 3,并不是站在你的角度考虑,而是为了推广Python项目。

这也是你不应该使用Python 3的根本原因。为什么他们向你推荐一个只有30%接受度、还在不断变动、充满问题的语言?原因是,越多的人从Python 3开始,意味着越多的接受度,这对Python 3有好处。他们不关心你的问题,不关心你遇到多么大的阻力,也不关心你因为Python 3的技术限制很可能学不下去。他们只担心你,一个Python的初学者,可能会壮大Python 3的使用者队伍。

我只希望你开始编程,不在乎你用什么语言。这就是为什么我有两本Python和Ruby免费书籍的原因。语言对我来说并不是很重要,我也不会向你推荐一门快要破产的语言。Python项目组却不在乎这么做,所以你应该避免上了他们的当,直到他们把问题都修补好。很多人在这条路上越走越远,甚至开始禁我的书,就因为此书的内容不支持Python 3。

换个角度说,如果Python 3真有那么好,他们也不需要说服你,你自然而然就会去用,也不会有什么问题。相反,他们没有把精力放在修复问题上,反而去宣传,利用社会压力和市场来说服你使用Python 3。从技术上看,他们会采取市场和宣传手段,恰恰说明Python 3是有缺陷的。

这种忽视Python 3本身存在的问题去宣传的行为,在我看来很不合理。

错误的决策

Python项目决定,Python 3将不兼容Python 2的代码。即使基础计算科学证明,新版本的语言兼容旧版本是可行的,但是他们还是声称“不可能”。鉴于他们控制着语言的实现,又故意让Python 3不兼容Python 2,我只好总结:他们也是故意让Python 3有缺陷的。这样做的结果是,与其选择花大工夫重写Python 2的代码,还要切换到一个截然不同的语言,大多数人选择不如就用Python 2吧。

这意味着如果你用Python 3,就是在一个很可能破产的平台工作,我个人是不能容忍教人们快要破产的东西的。

String难以使用

Python 3的String对初学者来说非常难用。其初衷是让Python更加国际化,但是却导致这个类型难以使用,错误信息很少。每次你想在程序中处理字符,都要搞明白Byte和Unicode的编码区别。不明白这是什么东西?错不在你。Python本来是一个简单的语言,对初学者非常友好,只需要简单的操作就能让程序工作,你可以一步一步地学习string。更糟糕的是,当String出错时(经常发生),得到的错误信息非常有有限,甚至不会告诉你哪一个变量需要修改。

除此之外,Python 3.6有三种不同的字符串格式化方式。这意味着你要学习三种完全不同的方法。即使是经验丰富的专业程序员,也不能轻松地记住这些方式和不断改变的feature。

我喜欢python的原因就是,它对初学者来说非常友好,不需要学习很深入地知识,马上就能写出有用的代码。而Python 3对国际化的尝试正好毁了这一点。

初学者开始编程,接触的第一件事就是String,Python使其变得如此复杂,像我这样的程序员都处理不好,很难让人坚持学下去。

核心库没有更新

很多核心库都用Python 3重写了,但是已经很久没有更新,新特性也没用上。Python 3变化如此之大,状态不稳定,让库怎么维护?在我自己的测试中,我发现当使用Unicode处理库的返回结果时就不能正常工作,得到的是Bytes。这意味着如果去使用一个你认为正常的库,说不定就会遇到一堆错误信息。这些错误信息是随机分布的,说不定有些能正确处理Unicode,却又在别的地方出错。

除非Python 3出一个标准,规定每一个库应该以什么样的编码返回结果,不然你永远不能依赖一个Python 3的库。解决方法是,创建一个“String”类型,能魔法般地感知字符串是Unicode编码的还是Bytes,但是Python上去反对任何他们认为是“魔法”的东西,所以他们坚持自己糟糕的设计,并声称这样做很好。

站在程序员的角度分析

这部分内容,初学者可以拿给你博学的朋友或大牛看。在这部分我用了很多代码示例,来展示为什么Python 3糟透了。目前的你可能对这部分内容有些疑惑,但是你的朋友能看的懂,而且很可能他们看了之后劝你不要碰Python 3。

开始之前,我想先声明一点,关于Python 3我已经写了一部分不错的教程,事实是我也想支持Python 3,但是在他们改良那些糟粕之前不会这么做,因为这违背了我的信念:初学者应该使用易用的东西。

Python 3虚拟机设计不良

不能兼容旧版本的代码是Python 3接受度不高的主要原因。下面是我的根据:

  • Python 3和Python 2一样使用了虚拟机
  • 本来Python 3代码是可以和Python 2代码在相同的虚拟机中运行的,它们控制着两种语言,完全可以做到兼容,但是却选择让用户手动转换代码
  • F#/C#和JRuby/Java就是很好的例子,前者同时控制着两种语言,后者没有,但是都做到了兼容
  • Java是向前兼容的另一个经典例子,也许对Python的迁移来说难度更小
  • 同一个虚拟机不能同时支持Python 2和Python 3,增加了Python 2代码的转化难度。更糟的是,Python项目组没有提供一个平滑的转换过程,而是让你去手动转换,同时支持两种语言
  • 当被问及为什么不同时支持两种语言时,Python项目的人告诉我,这是不可能的。但从技术上讲,这是完全可能的,而且这对代码迁移来说优点很多
  • Python 3的设计缺陷提高了迁移难度,降低了接受度,强迫你做代码转换工作。由此带来的问题,意味着Python 3有很大的可能性不会成功
  • 当我说“不会成功”的时候,人们可能想,这是相比于Python 2而言的,但是实际上我是相较于其他编程语言而言的
  • 对我而言,我认为因为设计上的不良,就需要人们耗费无意义的额外劳动是不合理的。我很怀疑他们是否能改正这些缺点,但是据上面这些证据,Python 3是不会成功的

故意挫败2to3翻译器

我曾经说过100%确定2与3兼容是可能的,像2to3转化这种东西没有必要。我觉得这种说法不妥,正确的说法应该是,“如果Python 2和3设计良好,那么2to3转换应该是能完美工作的。”

当然,我知道这并不算是一个有说服力的理由。代码转换高成本,以及不兼容Python 2,糟糕的2to3转换等,只是问题的一个表现,并不算得上是一个原因。

静态类型字符串

Python是一个动态类型的语言。这意味着我并不需要知道变量的类型就可以使用它。只要它表现得和另一种类型一样,我就可以把它当做是另一种类型。至于这是否符合计算机科学,就不重要了。动态类型的特性让Python变得易于使用,也是我将它推荐给初学者的原因。

在Python 3中,我不能像下面这样灵活地写代码了:

def addstring(a, b):
    return a + b

def catstring(a, b):
    return "{}{}".format(a, b)

如果我们有一个String和一个Bytes类型,那么调用第一个函数会得到一个error,第二个会得到一个用repr格式化的byte string。我比较倾向于第一个结果,至少会让我知道这个地方有问题,而不是在各个地方埋下隐患。

下面是我在Python 3.5中使用这两个函数的方法:

Python 3.5.1 (default, Sep 16 2016, 13:36:12)
[GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def catstring(a, b):
...     return "{} {}".format(a,b)
...
>>> x = bytes("hello", 'utf-8')
>>> y = "hello"
>>> catstring(x, y)
"b'hello' hello"
>>>
>>> def addstring(a, b):
...     return a + b
...
>>> addstring(x, y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in addstring
TypeError: can't concat bytes to str
>>> ^D

下面是Python 2的版本:

Python 2.7.11 (default, May 25 2016, 05:27:56)
[GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def catstring(a, b):
...     return "{}{}".format(a,b)
...
>>> def addstring(a, b):
...     return a + b
...
>>> x = "hello"
>>> y = bytes("hello")
>>> catstring(x, y)
'hellohello'
>>> addstring(x, y)
'hellohello'
>>>

关于为什么Python 3的版本表现和Python 2不同,没有技术上的原因,仅仅是因为他们决定让你手动去处理类型问题。经典的“Macho Code Guy”被抛弃了,这不再是适合初学者的一门语言。难用的东西并不是好的编程语言或好的程序员做出来的事情,而是傲慢的人做出来的事情。然而,当用户指出来这很难用的时候,他们的说法则是这对你好。这完全是对糟糕设计的辩解。

另外,错误提示也非常大男子主义,非常简短:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in addstring
TypeError: can't concat bytes to str

如果他们执意要求初学者弄明白什么是Bytes,什么是Unicode的话,至少他们要告诉人们哪些变量是Bytes,哪些是Strings。

动态与静态类型不匹配

string作为静态类型的一个致命缺点是,Python缺少相关处理的类型安全组件。Python是一个动态语言,函数声明的时候不支持声明类型。它也不是静态编译的,所以直到你运行代码之前,你都不知道是否存在类型错误。过去没有这些特性,也能正常工作,因为你可以写测试,Python动态的特性让Python只要方法类型的签名相同就能正常工作。

字符串也经常从外部资源得到,比如Socket连接,文件或者类似的输入。这意味着Python 3的静态类型字符串和静态类型安全缺失会导致Python 3程序崩溃更频繁,与Python 2相比将存在更多的安全问题。

核心库没有更新

抛开编码的问题不说,目前还有很多库,都不能正确地返回String结果。尤其是一些处理HTTP协议的库,很多情况下,库依然使用旧版的API,都依赖动态特性,但是在返回值方面没有更新。你以为返回的是String,但其实返回的是Bytes。

更愚蠢的是,Python原本有一个Chardet库,可以判断字节流的编码。如果Python 3非要像上面那样做,也可以将Chardet收入核心库,进行自动转换,让用户无需操心编码问题。这样的话,你就可以这样用:

x = bytes('hello', 'utf-8')
y = x.guess()

编码类型检测本来是一个已经解决的问题。其他语言差不多也都完美地处理好这个问题了。但是Python 3却认为,String的Unicode编码非常重要,用户应该自己处理。他们说易用性和安全性同样重要,但实际上并没有那么做。

太多的format选择

目前,Python 3支持旧的格式化风格:

print "Howdy %s" % ('Zed')

新的format风格:

print "Howdy {}".format('Zed')

Python 3.6会有一种新的format风格:

x = 'Zed'
print f"Howdy {x}"

我比较偏向最后一种,不知道为什么之前不这样支持,而是用愚蠢的format()函数。String插入应该是所有人用得最多一个特性。

现在的问题是,初学者必须了解目前所有的format格式,这实在是太多了。我在更新《笨方法学Python》(Learn Python the Hard Way)的时候,一般我只介绍Python 3.6的f-string风格,其他的只是提一下。

String的更多版本

最后,我听说现在又有一个新的String类型,同时支持Unicode和Bytes。这可能简化String的处理。但是我依然会让初学者避免学Python 3,直到“chimera string”能依赖动态很好地工作。在这之前,你都需要艰难地在一个动态语言里处理一个静态类型。

总结和警告

我已经坚持写Python 3存在的问题很久了,期间经过了五个版本。因为我希望它能为初学者带来帮助。每年我都尝试将我的一些代码用Python 3重写,都会遇到挫折。如果我都不能熟练地使用Python 3,那么对初学者来说就更不可能了。所以每年我都必须等其修复问题。我是真心喜欢Python,并且希望Python项目组能放下傲慢的态度,更加注重易用性。

这五个版本之后,我必须承认,Python 3已经不再适合初学者了。我希望这份列表能带来一些实质性的改变,但我对此并不抱太大期望。随着Python 3的开发,接受度却没有提高。Python 3项目组对此的措施是利用社会压力,市场鼓动来获得接受度,而不是去修复Python 3存在的问题。这很尴尬,修复技术上的问题很简单,但是错误地看待社会反应带来的问题就难对付了。

现在,Python社区已经被洗脑了,他们认为目前的String很好,Python 3不兼容Python 2是一种特殊的爱,甚至去反对语言翻译背后的数学,尝试用蛮力去转换Python 2代码。

残酷的现实是,如果Python 3被设计成同时支持Python 3和Python 2代码,String也和Python 2一样是动态的,情况就不会像现在这样。现在,我害怕的情况是,很多Python用户更倾向于使用一个稳定的语言,比如Go、Rust、Clojure或Elixir。切换到Python 3的成本太高了,倒不如直接切换到另一种不会破产,更稳定的语言。

也许Python项目更专注于让初学者学Python 3,是因为对这些人来说,没有什么转换成本。如果你按照他们的方式去学习,在Python 3的坑里摸索,那么你就忽视了其他可能的选择。他们牺牲初学者的利益去拯救一个需要修复的项目,这是不道德的。这意味着如果初学者学习编程失败了,那么不是他们自己的问题,而应该归咎于Python 3。

从我的意愿出发,我希望这篇文章能给他们一些警示,能改变他们做事情的方式,对别人友好一些。承认他们在Python 3设计上的瑕疵,在Python 4上拯救这个语言。遗憾的是,我很怀疑这个愿望是否能成真,很可能他们会觉得我在说些什么,到底懂不懂Python 3,认为我应该闭嘴。

无论如何,我都会继续用我认为最简单、最好的方式教Python 3,带给初学者学习编程的热情。

感谢阅读。

原文The Case Against Python 3 (For Now)
作者:Zed A. Shaw 翻译赖信涛 责编:仲培艺

评论