如何为使用 Python 语言而辩论
最近我写了一篇关于我为什么不担心Python流失用户的文章。几分钟之后有人问我Python的用法(usage),而这篇文章没有提及,但却是一个让人深思的问题。我们看到,使用Python的用户很可能在未来保持高位,但是Python是否会被用到尽可能多的项目中是不能保证的;用户(users)数目很多而且稳定,但是项目中Python的用处(use)并不确定。
这篇文章的用意是帮助表明Python仍然对大多数软件项目是切实可行的。我不担心把Python推销给反对其他动态语言(如Ruby)的人,因为我认为这些争论与个人喜好有关。这篇文章是讲给那些推销静态类型语言的人。具体上,这篇文章是针对Go的,但也可以是其他任何静态类型语言。
“为什么Go?”,你可能会问。因为Go实际上在获取Python的用户。当2003到2005年间Python的增长曲线是个曲棍球棒时,Python 还不是被推下山巅的王者,而是个弱者。传统上,Python从Java之类的语言阵营中获得用户,并且留住了他们(我不想谈C++用户,因为通常他们有严格的性能需求,需要一个系统语言,或者是性能成瘾者,并且需要好好恢复)。但是Go的情况不太一样。如今Python是使用最多的语言之一,而不再是弱者了。一旦在静态类型语言社区中出现一门语言,它的生产效率/性能的取舍相当好,那便足以说服一些Python的程序员选择Go而不再是Python了。
如今的Go
首先我应该说,Go是目前我第二喜欢的语言。如果今天我要启动一个项目,但不能说服人们使用Python,那我会提议使用Go。不要误解我在本文中说Go 是门不好的语言。这篇文章的要点是说服其他人,Python是生产率/性能取舍游戏中Go之外切实可行的替代方案,而不是表达Go是门不好的语言。认为这篇文章是反Go的,那就是你的个人想法,而且不应该这样认为。
我应该说,我偶尔在工作中使用Go,并有点想关注这门语言的社区。既然我不能仅凭想象就成为Go专家,但这番话并不是仅从文档或者博客中提取出来的。但是由于我是Python开发团队的一份子,无论我如何试图表现得公平,固有的偏见某种程度上还是有的。
那么,带着这些警告,我们来看下Go提供给开发者什么。
生产率
我看待Go的方式是,使用你最喜欢的编程语言,移除那些难于加速生产率的特性,就是Go。静态类型的影响被降到最小,因为通常只有在API边界时你才会面对它。结构类型同样使事情变得简单(把它认为是鸭子类型)。语法并不笨拙(虽然它使用了花括号)。不要认为Go是C/C++去掉不安全的特性,加上生产率更高的东西,不然你会很失望(比如,“为什么我不能使用make()内置函数,也不能像map类型一样对返回值进行计数”,这种看待Go的方式是错误的;这就是为什么C++开发者没有转到Go的原因)。快速编译也使开发周期更像一个动态语言,而不是一个需要编译的语言。而且事实上有些人喜欢没有异常机制带来的冗长,因为这促使你处理每种异常情形而不是(意外地)忽略它们(这是贯穿Go初始系统语言设计的实例)。还有,这门语言本身相当短小易记,并有严格的前向兼容性要求(forward-compatibility requirements)(你不可能更快地获得泛型),大体上使用Go来编码是件很愉快的事情。
由于是静态类型,Go可以很容易地获得工具支持(它对之前以此为设计目标的语言也有帮助)。Go确保核心工具跟随Go本身提供,也是明智之举。go fmt强制执行Go风格的规则,并允许通过用户自定义的规则来重构代码("采用制表符缩进"不再是问题,因为这意味着你可以随心所欲地设置编辑器来代表制表符,然后go fmt将其转换为普通制表符以适用VCS)。go fix会更新代码以跟最新发布的版本保持一致。go get获取依赖并安装。
Go最后一个生产率功能是它静态编译所有东西,使部署更简单。如果你使用容器来开发和部署,这也不算什么。只有当你发布单个文件的命令行工具,而不是一组依赖和你自己的代码时,这才算得上事。
性能
就性能来说,Go做的很好。很难指出任何基准能准确的证明Go总是最快的选择,甚至计算机语言基准游戏中一些基准证明CPython 3是最快的。但是通常情况下可以认为对于你的任何工作来说Go已经足够快了。
Go真正出色的地方是并发性(concurrency) 。要注意并发代码并不是通常误解的并行(parallelized)代码; 并发代码仍然可以是单线程的,仅仅在任务切换方面更加简单/出色。Go通过使用goroutine使连续并发的代码执行起来绝对的简单。如果你不想使用共享内存的方式(虽然也同样支持),该语言提供的通信管道允许以非常简洁的消息传递方式进行并发编程。将所有特征整合进此语言中成为尽可能使用该语言开发并发代码的又一原因。换句话说,Go程序运行很快,该语言尽力使你在合理的方式上获得该效果。
如今的Python
如果顺利的话我已经让你相信Go是一种优秀的编程语言,除非因为其他原因,一些人不会认为我在整篇文章对Go的描述很糟糕。现在我们讨论一下Python的生产率/性能是怎么样的。
生产率
首先也是最重要的,Python非常容易学习。这也是为什么在当前高评价的美国大学中将Python作为首选的教学语言 。这相当于该语言拥有成熟稳定的新程序员的来源以及更容易培训其他程序员。 我想,要说服别人只用几行Python代码就会完成很多工作这并不难(Go/Python 3比较 显示Python每次都比Go使用更少的代码完成相同的工作)。所以我会坚持认为使用Python会更高产,即使和Go相比,这不会有人反对。
通常大家反对Python的地方是在工具支持方面。但是如果你注意到我指出的Go相关的支持工具,fmt, fix, 和 get, Python社区也有对等的工具。对遵循PEP 8的风格格式化(style formating), 可以在提交检查时使用pep8,或者如果想要更多go fmt风格的自动重写可以使用autopep8。对用于重构的go fix或go fmt,你可以说2to3也可以完成同样的功能。对于go get, Python有pip。我们有venv/virtualenv或cx_Freeze这样的代码冻结工具(跟其他一样,位于容器之上?on top of containerization like anything else),而不是静态编译的二进制包。甚至有贯穿项目的代码分析工具如pylint。说Python因为缺少工具支持而不能用于大型项目,这种观点对我来说是很肤浅的。
如果说有哪方面Python完全做的好,那就一定是它丰富的第三方扩展库和相应的工具可供使用,就像在PyPI上面看到的那样(我相信肯定有人忍不住要争论说,“并不是所有的第三方库都能够在Python3上面运行啊”,事实确实如此,然而,这些第三方扩展库对Python3的支持已经相当好了,而且还在继续改善中,所以我不会太在意这个争论,另外,你可以同时使用Python2/3两个版本进行编码,不需要关心针对哪个版本)。看一下 godoc.org,上面显示Go也并不缺少社区支持,Pytho之所以能够拥有更多可用的第三方库仅仅是因为它的年龄,这个状态也会继续持续。
性能
因为Python已经存在很久,且变得如此庞大, 简单地去说 “Python是足够快的” 不能说明整个的情况, 那是因为有各种各样的实现加速的方式。但是在深入到VM级别的选项之后,意味着Python的stdlib提供了获得加速的选项。举例来说, concurrent.futures 是尴尬地执行并行代码的方式,这种方式是极其简单的。而在Python 3.3中,新的asyncio编写了异步代码。它没有像Go那样被集成进语言,在Python中的并发程序设计是可行的,且在方式上也未必是那么痛苦的。
但是最好的办法是,你可以在选择的VM里改变Python代码的性能。
CPython + Cython
如果你在使用 C 拓展模块,CPython 就会使你最好的选择(可能你不知道这个术语,CPython 是你可以在 python.org 获得的解释器)。对大多数的情况而言性能至少合理些– 因为某些原因,一些人认为Python 开发团队不关心性能,这个一个谎言 – 而且即将会成为新的特性,因为 CPython 同时担当着语言规范的作用。
如果认为你的一些内循环代码确实需要提高些速度, Cython 是 CPython 的选择。Cython 会尽可能的将你的 Python 代码编译成 C 拓展代码。有若干种支持的方法可以产生更好的 C 代码,所以这取决于你需要怎样的 Cython 特性。Cython 同时也使写出 C 拓展模块更加简单(但要继续读下去,除了CPython 还有其他的选择)。
PyPy + cffi
如果你不依赖于已存在的 C 拓展模块,PyPy 会给你提供总体上最好的性能。它的JIT非常好而且它的团队欢迎受到使用 CPython 并且运行更快的代码的挑战,因为他们痛恨在 speed.pypy.org 中显示的那么慢。实话说,除非PyPy不支持你真的想用的那个版本的 Python – 因为 PyPy 确实会落后2个版本, 比如pypy3 现在支持 Python 3.2 然而 3.4 是最新的 CPython 发布版; 它们期盼在这个问题上能得到帮助(donation) – 我只能考虑不使用PyPy因为你依赖于已存在的C拓展模块C( numpy 是最常见的问题,虽然 PyPy is looking for donations 可修复这个问题)。
但这不意味着,如果你想封装一些C代码就用不了PyPy。PyPy项目还有另外一个子项目cffi,这个项目的目的是使Python代码也可以利用封装的C代码。使用cffi的关键好处在于,一旦你使用了cffi,C代码就可以用于CPython和PyPy(我认为IronPython和Jython也在添加对cffi的支持)。所以如果你在封装C代码,我强烈建议你看下cffi,而不是手动写C扩展模块或者使用Cython,这样你有更好的Python实现的支持,还能使用PyPy。
Numba
如果你在做数值相关的工作,你肯定应该考虑Numba这个选择。在科学计算上,经济学家注意到了它的性能。虽然在普通的Python编程上,它不能帮到什么忙,但是如果在Python非常强大的科学计算栈中用到了numpy或者其他模块,Numba使用LLVM来进行JIT肯定会有帮助。
未来的 Python
考虑到所有内容,Python肯定不是停滞不前的(Go也没有,比如它们都在忙于用Go重写编译器和将连接器以外的东西转移进编译器来获得更快的编译速度)。Python 的未来看起来还是光明的。
生产率
Python 是一种在进化的语言。不像 Go,Python 乐于改变该语言,甚至是以永远不再向后兼容的方式。这意味着Python会比Go更快的速度变得更加高效(虽然在Go 2开发之前Go的团队对该语言进化持哪种观点还是未知的)。
在工具方面,标准化的 函数注解 是为了声明类型。 这是在 PyCon 2014 语言峰会期间提出的,针对函数参数和返回值,里面提到有大量的项目现在想要有一种声明预期类型的方法,使用函数注解考虑到了在某些方面的标准化,最终可能对标准库函数也会是有用的。在pytypedecl的邮件列表上的讨论还没有开始,但是我知道PEP大概是要开始了。不仅仅是对像Cython和Numba这样的项目在什么地方使用打印信息,还包括在诸如代码分析,重构等的时候使用。
性能
长远看来,有两个项目可以帮助提升Python的性能。一个是新的Python虚拟机Pyston。尽管Pyston刚出现时间不长,但它的目标是要使用LLVM的JIT(是的这不由得让人想起Unladen Swallow,然而LLVM的JIT已经比它在2009年时好很多了,所以这个项目还是颇有希望取得好效果的)。
其实PyPy-STM才是真正能够让我兴奋的项目,"STM"表示"软件事务性内存",它基本上是允许Python丢弃GIL的。PyPy-STM此时的性能比PyPy要慢大约1.2-3倍,这样的表现已经相当不错了。目前他们正在寻找资助,来继续这项工作,要实现这个目标:使得带有两个线程的PyPy-STM值得在PyPy上普遍运行。
在黑暗中做出选择
希望这篇博客传达的不是一个总结, 而是一个关于生产力/性能折衷的方案。 Python已经清晰地拥有了强大的生产力辅助并且没有哪个领域表现不佳,这仍是我选择的语言。如果你发现自己有可能选择Python项目以外的东西,请一定要停下来思考没有使用Python所带来的生产力损失,然后看看你有各种选项让Python加快执行,这样你再去做一个全面的关于Python是否可以为你的项目工作的选择。