Python易用性与Go简单性比较


Python 和 Go 有着截然不同的特质,可以相互补充:
Python is Easy. Go is Simple. Simple != Easy

有一种常见的误解是,简单和容易指的是同一件事。毕竟,如果一个东西很容易使用,那么它的内部结构也一定很容易理解,对吗?或者反之亦然?事实上,情况恰恰相反。虽然这两个概念在精神上指向相同的结果,但要使某样东西外表看起来简单,其内部却需要巨大的复杂性。

以 Python 为例,这种语言以入门门槛低而著称,因此也是入门编程语言的首选。全球的学校、大学、研究中心和大量企业之所以选择 Python,正是因为它对任何人都很容易,无论其教育水平或学术背景如何(或完全没有)。人们很少需要太多的类型理论,也不需要了解内存中的东西是如何存储的、存储在哪里、某些代码是在哪个线程上运行的等等。此外,Python 还是进入一些最深奥的科学和系统级库的入口。只需一行代码就能控制如此强大的功能,这充分说明 Python 将成为地球上最流行的编程语言之一。

但问题来了--用 Python 代码轻松表达事物是有代价的。在引擎盖下,Python 解释器非常庞大,即使是执行一行代码也必须进行许多操作。当你听到有人说 Python 是一门 "慢 "语言时,你所感受到的 "慢 "大部分来自于解释器在运行时所做出的大量决定。但在我看来,这还不是最大的问题。Python 运行时生态系统的复杂性,加上在软件包管理方面的一些自由的设计决定,使得环境非常脆弱,更新经常导致不兼容和运行时崩溃。离开 Python 应用程序几个月后再回到它身边,却发现主机环境已经发生了足够大的变化,甚至再也无法启动应用程序,这种情况并不少见。

当然,这也太简单化了,现在的孩子都知道,容器的存在就是为了解决这样的问题。的确,多亏了 Docker 及其类似产品,我们才有可能及时 "冻结 "Python 代码库的依赖关系,使其几乎可以永远运行下去。然而,这样做的代价是将责任和复杂性转移到操作系统基础架构上。这不是世界末日,但也不容小觑和忽视。

从轻松容易到简单
如果我们用 Python 来解决这些问题,我们最终会得到类似 Rust 的东西--性能极高,但入门门槛却出了名的高。在我看来,Rust 并不好用,更不简单。虽然现在 Rust 被炒得沸沸扬扬,但尽管我已经有 20 年的编程经验,并且已经初步掌握了 C 和 C++,我还是无法在看到一段 Rust 代码时就肯定地说,我理解了其中的含义。

大约五年前,我在开发一个基于 Python 的系统时发现了 Go。虽然我试了几次才喜欢上它的语法,但我立刻就喜欢上了它的简洁理念。Go 的目的是让组织中的任何人都能简单理解,从刚从学校毕业的初级开发人员到只是偶尔看看代码的高级工程经理。更重要的是,作为一种简单的语言,Go 的语法更新非常少--最近一次重要的更新是在 v1.18 中添加了泛型,而这是经过十年的认真讨论才完成的。在大多数情况下,无论你看的是五天前还是五年前编写的 Go 代码,大部分都是一样的,都能正常运行。

不过,简约需要纪律。一开始,它可能会让人感觉受到限制,甚至有些落后。尤其是与简洁的表达式(如 Python 中的列表或字典理解)相比:

temperatures = [
    {"city": "City1", "temp": 19},
    {
"city": "City2", "temp": 22},
    {
"city": "City3", "temp": 21},
]

filtered_temps = {
    entry[
"city"]: entry["temp"] for entry in temperatures if entry["temp"] > 20
}

同样的代码在 Go 中需要多敲几下键盘,但在理想情况下,应该更接近 Python 解释器在引擎盖下所做的事情:

type CityTemperature struct {
    City      string
    Temp float64
}

// ...

temperatures := []CityTemperature{
    {
"City1", 19},
    {
"City2", 22},
    {
"City3", 21},
}

filteredTemps := make(map[string]float64)
for _, ct := range temperatures {
    if ct.Temp > 20 {
        filteredTemps[ct.City] = ct.Temp
    }
}

虽然您可以用 Python 写出相同的代码,但编程中有一条不成文的规定,即如果语言提供了更简单(更简洁、更优雅)的选择,程序员就会倾向于使用它。但 "简单 "是主观的,而 "简单 "应该同样适用于每个人。执行相同操作的替代方案导致了不同的编程风格,而且在同一代码库中往往能发现多种风格。

由于 Go 语言冗长而 "乏味",它自然而然地打上了另一个"√"--Go 编译器在编译可执行文件时的工作量要少得多。编译和运行 Go 应用程序的速度通常与运行实际应用程序之前加载 Python 解释器或 Java 虚拟机的速度相当,甚至更快。毫不奇怪,作为一个本地可执行文件,它的速度是一个可执行文件所能达到的最快速度。虽然速度比不上 C/C++ 或 Rust,但代码复杂度却只有它们的几分之一。我愿意忽略 Go 这个小小的 "缺点"。最后但并非最不重要的一点是,Go 语言的二进制文件是静态绑定的,这意味着你可以在任何地方构建一个二进制文件,然后在目标主机上运行--不需要任何运行时或库依赖。为了方便起见,我们仍然将 Go 应用程序封装在 Docker 容器中。

不过,与 Python 或 Java 应用程序相比,Go 应用程序的体积要小得多,内存和 CPU 消耗也少得多。

我们如何利用 Python 和 Go 来发挥我们的优势#?
我们在工作中找到的最实用的解决方案是将 Python 的易用性和 Go 的简单性结合起来。对我们来说,Python 是一个很好的原型开发平台。这里是创意诞生的地方,也是科学假设被接受和拒绝的地方。Python 与数据科学和机器学习有着天然的契合点,由于我们处理大量此类事务,因此尝试用其他工具重新发明轮子意义不大。Python 也是 Django 的核心,这也体现了 Django 的座右铭,即允许快速开发应用程序,这一点是其他工具所无法比拟的(当然,Rails 上的 Ruby 和 Elixir 的 Phoenix 也值得一提)。

假设一个项目需要一点用户管理和内部数据管理(就像我们大多数项目一样)。在这种情况下,我们会从 Django 骨架开始,因为其内置的管理员功能非常出色。一旦粗略的 Django 概念验证开始像一个产品,我们就会确定有多少内容可以用 Go 重写。由于 Django 应用程序已经定义了数据库的结构和数据模型的外观,因此在此基础上编写 Go 代码就非常容易了。经过几次迭代后,我们达成了共生关系,双方在同一个数据库上和平共处,并使用基本的消息传递进行通信。最后,Django "shell "变成了一个协调器--它为我们的管理目的服务,并触发任务,然后由 Go 对应的部分来处理。Go 部分则为其他一切提供服务,从面向前台的应用程序接口和端点到业务逻辑和后台作业处理。