Heim  >  Artikel  >  Backend-Entwicklung  >  Python 开发者在迁移到 Go 前需要知道的事情

Python 开发者在迁移到 Go 前需要知道的事情

高洛峰
高洛峰Original
2016-10-20 09:33:081015Durchsuche

这是一篇记录了我们把一大段 Python/Cython 代码迁移到 Go语言经历的(长)博客。如果你希望了解整个故事、背景等所有的内容,请接着读。如果只对 Python 开发者进入之前需要知道的东西感兴趣,点击下面的链接:

从 Python 迁移到 Go 的提示与技巧

背景


我们在 Repustate 技术上最大的成果就是实现了阿拉伯语情感分析。阿拉伯语真是一个难啃的骨头,它的单词语法形态太复杂了。阿拉伯语的分词(tokenization,把一个句子分成独立的词语)相比诸如英语更难,因为阿拉伯语单词内部可能包含空格(例如,aleph内的位置)。这个不需要保密,那就是 Repustate 使用了支持向量机(SVM)来得到句子最可能的意思,然后在此基础上分析情感。我们总共用了 22 个模型(22 个支持向量机),文档中的每个单词都会被分析。也就是说,如果一篇文档包含 500 词的话,将会有一万次以上的支持向量机的比较操作。

Python


Repustate几乎是彻底用 Python 实现的,因为我们使用了 Django 作为应用程序接口和网站架构。所以只能保持代码的统一,同时用 Python 实现整个阿拉伯语情感引擎。原型与实现的过程中,Python 还是很不错的。非常强的表达能力,强大的第三方库资源。如果你只是服务于网页的话,还是很完美的。但是,当你需要进行底层计算,需要在散列表(Python 中的字典)上进行大量的比较运算的时候,速度就慢下来了。我们每秒钟只能处理2到3篇阿拉伯语文档,这太慢了。对比我们的英语情感引擎,每秒钟能处理 500 篇文档。

瓶颈


于是,我们启动了 Python 分析器,研究哪部分执行得慢。还记得我说过我们会用 22 个支持向量机处理每一个单词吗?这些处理都是串行的,没有并行操作。好,我们第一个想法是把这个改成类似 map/reduce 的操作。长话短说:Python 中不适合使用 map/reduce。当你需要并发性的时候,Python 一点都不好用。2013 年的 PyCon 大会上,Guido 提到了 Tulip,他试图解决这个问题的新项目,但是还需要一段时间才能推出。如果已经有更好的选择,我们为什么还要等它呢。

换 Go 语言还是回家种田


我在 Mozilla 的朋友告诉我,Mazilla 服务中日志架构的大部分代码已经切换成 Go 了,部分原因是 goroutine(Go 线程)的强大。Go 是 Google 的一群人设计的,它把并行性作为一级概念,而不像 Python 的不同解决方案做的事后补充。于是,我们开始着手把 Python 换成 Go。

尽管 Go 代码还没达到产品级别,其结果已经非常令人鼓舞了。我们达到了每秒 1000 文档的速度,使用了更少的内存,还不用去处理用 Python 时碰到的多进程/gevent/“为什么 Ctrl+C 杀掉了我的进程”代码等讨厌的问题。

我们为什么爱上了Go


只要知道一点儿编程语言工作原理的人,(明白解释和编译以及动态与静态的区别),就会说:“老兄,Go 显然会更快”。没错,我们也可以把整个东西用 Java 来重写,并且得到类似的性能,但这不是 Go 胜出的原因。你用 Go 写出代码来就很容易是正确的。我也说不清楚怎么回事,但是一旦代码编译通过(编译速度还很快),你就感觉到它可以工作了(不只是运行不提示错误,而是逻辑上就是对的)。我知道这听起来很玄乎,但确实是事实。这就像 Python 解决冗余问题(或者说无冗余),它把函数作为一级对象,从而函数编程可以轻松的进行。go线程和通道(channel)让你的生活如此轻松。你还可以得到静态类型带来的性能提升,更精确的控制内存分配,却不会因此损失表达性。

我们早该知道的事情


除去那些赞美之词,用 Go 的时候需要一种不同于用 Python 时的心态。下面是一些迁移时候的笔记,把 Python 转成 Go 时随机跃入我脑子的东西:

没有内建的集合类型(需要使用 map 然后检查存在性)

由于没有集合类型,需要自己实现交集、并集等方法

没有元组(tuple),需要设计自己的结构(struct)或者使用slice(类似数组)

没有类似 __getattr_() 的方法,需要你检查存在性而不能设置缺省值,例如 Python 中,你可以这么写:value = dict.get("a_key", "default_value")

需要检查错误(或者至少显式的忽略它们)

不能够有未使用的变量和包,需要时不时的注释掉一些代码

在 []byte 和 string 之间切换,正则处理(regexp)使用 []byte(可改写的)。这是对的,但转换来转换去还是很麻烦

Python 语法更宽松。你可以用超出范围的索引取字符串的片段而不出错,也可以使用负数取片段。Go 就不行。

无法使用混合类型的数据结构。这可能不一定合适,但是 Python 中有时候我会有一个取值可以是字符串和列表混合的字典。Go 里不行,你必须清理里的数据结构或者自定义结构*

没法把元组或者列表分配成分开的变量(例如,x, y, x = [1, 2, 3])

驼峰式大小写习惯(首字母不大写的函数/结构不会暴露给其他包)。我更喜欢 Python 的小写加下划线的习惯。

必须显式的检查错误是否为空,不像 Python 中很多类型都可以像布尔类型一样的用(0,空串,None都可以作为布尔“假”)

一些模块(如 crypo/md5)的文档不足,但是 IRC 上的 go-nutes 很厉害,有强大的支持

数字转字符串(int64->string)与 []byte 转字符串(只要 string([]byte))不同,需要调用 strconv

读 Go 的代码绝对像是编程语言,而 Python 可以写成像是伪代码一样。Go 使用更多的非英文数字字符,使用 || 和 && 而不是 or 和 and。

写文件会有 File.Write([]byte) 和 File.WriteString(string),与 Python 开发者的一种办法解决问题的信条不一致。

字符串插入不好用,必须经常使用 fmt.Sprintf

没有构造函数,通常的习惯是写一个 NewType() 函数返回你要的结构

Else(或者 else if)得正确的格式化,else 得和与 if 配对的大括号在一行。奇怪。

函数内外使用不同的赋值操作符,= 和 := (译者注:此为作者的误解,= 和 := 的区别是显式定义类型还是自动类型推导,而函数外的变量只能用 =)

如果我只想要键值(dict.keys())或取值(dict.values())的列表,或者元组的列表(dict.items()),Go 中没有对应的函数,只能自己迭代


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn