Python 和 Go 具有不同的特性,可以相互补充。
有一个常见的误解,简单和容易指的是同一件事。毕竟,如果一个东西很容易使用,那么它的内部工作原理一定很容易理解,对吗?或者反过来?事实上,恰恰相反。
虽然这两个概念在精神上指向相同的结果,但要让一些东西看起来很容易,在引擎盖下需要巨大的复杂性。
以 Python 为例,它是一种以低门槛而闻名的语言,因此是入门编程语言的最佳选择。地球仪上的学校、大学、研究中心和大量企业选择 Python 正是因为它对任何人都是可访问的,无论他们的教育水平或学术背景如何(或者完全没有)。
人们很少需要太多的类型理论或理解事物如何以及在哪里存储在内存中,某段代码在哪个线程上运行,等等。
此外,Python 是通往一些最深刻的科学和系统级库的入口。
能够用一行代码控制如此强大的功能,这说明它很有可能成为这个星球上最流行的编程语言之一。
这里有一个问题--用 Python 代码表达东西的容易性是有代价的。
在后台,Python 解释器是巨大的,即使是一行代码也必须执行许多操作。
当你听到有人说 Python 是一种“慢”语言时,很多人认为 Python 的“慢”来自于解释器在运行时所做的决定的数量。但在我看来,这还不是最大的问题。
Python 运行时生态系统的复杂性,以及围绕其包管理的一些自由设计决策,导致了一个非常脆弱的环境,更新经常导致不兼容和运行时崩溃。
离开一个 Python 应用程序几个月后回到它,才意识到宿主环境已经改变得足够多,以至于不再可能启动应用程序,这并不罕见。
当然,这是一个严重的过度简化,甚至现在的孩子都知道容器的存在就是为了解决这样的问题。事实上,多亏了 Docker 和它的同类,可以及时“冻结”Python 代码库的依赖项,以便它实际上可以永远运行。
然而,这是以将责任和复杂性转移到操作系统基础设施为代价的。这不是世界末日,但也不能低估和忽视。
从轻松到简单
如果我们用 Python 来解决这个问题,我们最终会得到像 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 容器中。
尽管如此,它们还是要小得多,而且内存和 CPU 消耗量只是 Python 或 Java 的一小部分。
我们如何同时使用 Python 和 Go 来发挥优势
我们在工作中发现的最实用的解决方案是将 Python 的易用性和 Go 的简单性结合起来。对我们来说,Python 是一个很好的原型开发平台。它是思想诞生的地方,也是科学假说被接受和拒绝的地方。
Python 是数据科学和机器学习的天然选择,由于我们要处理很多这样的事情,尝试用其他东西重新发明轮子是没有意义的。
Python 也是 Django 的核心,它的座右铭是允许像其他工具一样快速开发应用程序(当然,Ruby on Rails 和 Elixir 的 Phoenix 值得一提)。
假设一个项目需要一点点的用户管理和内部数据管理(就像我们大多数项目一样)。在这种情况下,我们将从 Django 开始,因为它内置了 Admin ,这很棒。一旦 Django 的概念验证开始类似于一个产品,我们就可以确定其中有多少可以在 Go 中重写。
由于 Django 应用程序已经定义了数据库的结构和数据模型的外观,因此在其上编写 Go 代码非常容易。经过几次迭代,我们达到了一种共生关系,双方在同一个数据库上和平共处,并使用最基本的消息传递来相互通信。
最终,Django 的“shell”变成了一个协调器--它服务于我们的管理目的,并触发任务,然后由 Go 的对应部分处理。Go 部分服务于其他一切,从前端 API 和端点到业务逻辑和后端作业处理。
到目前为止,这是一种共生关系,我希望它在未来保持这种状态。在以后的文章中,我将概述一些关于架构本身的更多细节。