侧边栏壁纸
  • 累计撰写 121 篇文章
  • 累计收到 2 条评论

从 Python 跳到 Go 写了半年后端,这 6 个差异让我栽了不少跟头

2026-5-23 / 0 评论 / 18 阅读
🤖AI摘要
本文作者分享了从Python转向Go编写后端服务的半年经验,指出了六个主要差异。首先是Go的nil和Python的None在处理方式上的不同,Go的nil直接导致panic。其次,Go的error处理强调显式错误检查,而非Python的try/except。第三,Go的goroutine虽然入门简单,但并发编程的复杂性并未降低,需注意内存共享和数据竞争问题。第四,Go中的struct与JSON的字段映射需要使用struct tag,与Python的json.dumps()不同。最后,Go在处理结构体和JSON字段映射时需要注意零值处理。作者通过这些差异,提醒开发者在使用Go时需要适应其特定的编...

之前一直用 Python 写后端,Flask、FastAPI 换着用,项目大了之后总感觉跑起来越来越重,部署也要额外操心。后来接了个对性能要求比较高的内部工具,想着正好拿 Go 试试水,结果这一试就试了半年。

半年下来不能说精通,但踩过的坑够写一篇了。挑几个印象最深的说说。

1. 空指针跟 Python 的 None 根本不是一回事

刚开始写 Go 的时候最容易犯的错就是这个。Python 里你拿个 None 调方法最多抛个异常,catch 一下就完事。Go 里 nil 上调用方法直接 panic,整个进程给你崩掉。

var m map[string]string
m["key"] = "value"  // panic: assignment to entry in nil map

还有 nil 的 slice 可以 append,但 nil 的 map 不行——这个不一致性第一次碰到的时候 debug 了半小时。后来养成习惯:声明 map 就用 make(),拿指针之前先判 nil。

Python 的 None 是设计成"友好的不存在",Go 的 nil 是"你碰我就炸"。心态上得转过来。

2. 错误处理不是 try/except,是每个函数调完都要 if err != nil

这是刚从 Python 转过来最不习惯的地方。Python 里抛异常很随意,上层统一 catch 就行。Go 的哲学是"错误就是值",每个可能出错的地方你都得处理。

file, err := os.Open("config.yaml")
if err != nil {
    return err
}
defer file.Close()

data, err := io.ReadAll(file)
if err != nil {
    return err
}

一开始觉得这太啰嗦了,写个文件读要写三个 if err != nil。但写久了发现其实有好处:每个错误点都是显式的,review 代码的时候不会漏。Python 里你可能漏了 try 或者 catch 范围太宽,出了异常定位半天。Go 是逼你直面每个错误。

后来我不纠结了,反而觉得这样心里有底。写个 helper 函数把重复的 if err != nil { return } 包装一下,能省点键盘。

3. goroutine 看起来简单,并发编程的门槛其实没降

很多人说 Go 的 goroutine 比 Python 的 asyncio 简单。我的体会是:入门确实简单,go func() 一开就完事,但写对很难。

Python 的 asyncio 是单线程的,你起码得理解 event loop 和 await。Go 的 goroutine 是真并发,共享内存的问题一个都避不开。我第一次写并发下载器的时候,开了一堆 goroutine 往同一个 map 里写数据,跑了几次就 data race,debug 了好一阵才发现要用 sync.Mutex 或者 channel。

var results map[string]int
var mu sync.Mutex
var wg sync.WaitGroup

for _, item := range items {
    wg.Add(1)
    go func(i string) {
        defer wg.Done()
        val := fetch(i)
        mu.Lock()
        results[i] = val
        mu.Unlock()
    }(item)
}
wg.Wait()

还有 goroutine 泄漏——开了 goroutine 结果 channel 没人读或者没人写,就永远阻塞着。Python 的 asyncio 里 task 没 await 会有警告,Go 的 goroutine 泄漏了啥提示没有,内存一直涨还不好查。

建议:goroutine 一定要想清楚退出条件,最好用 errgroup 或者 context.WithCancel 控制生命周期。

4. 结构体和 JSON 的字段映射,struct tag 绕不过去

Python 里 json.dumps() 一把梭,字段名直接序列化。Go 你得给每个字段写 struct tag:

type User struct {
    Name    string `json:"name"`
    Email   string `json:"email"`
    Age     int    `json:"age,omitempty"`
}

不写 tag 的话,默认用大写字段名——JSON 也是大写,就显得很不对劲。而且 omitempty 要注意:零值(0、false、空字符串)会被忽略。有一次 age=0 的用户被接口返回丢了这个字段,前端以为没传。

Python 的动态特性在这种场景下确实方便很多。不过 Go 的 struct tag 写完了类型安全、序列化可控,各有利弊。

5. interface{} 不是 any,空接口的坑比想象中多

Go 1.18 有了泛型之后好多了,但老项目里 interface{} 还是到处可见。Python 程序员习惯了一切皆对象,到了 Go 里发现空接口虽然能接任何值,但拿出来用之前得类型断言:

var val interface{} = "hello"
s := val.(string)         // 不安全,panic
s, ok := val.(string)     // 安全做法
if !ok {
    // 类型不对
}

而且 interface{} 的比较也有坑:两个都是动态类型可比较的 interface{} 可以 ==,但如果里面装了个 slice 或 map,就会 panic。Python 里随便比较两个对象,顶多返回 False。

泛型出来之后这些场景好了一些,但如果一个函数参数用 any(其实就是 interface{}),调用方能传任何东西过去,编译能过、运行可能崩。类型安全打了折扣。

6. 依赖管理和编译,体验比 Python 好但也有自己的问题

Python 的 pip + virtualenv 或者 poetry,依赖一多经常碰壁:版本冲突、锁文件不一致、跨平台编译折腾。Go 的 module 系统本身清爽很多,go mod tidy 一把梭。

但 Go 的 module proxy 在国内访问是个问题。虽然可以配 GOPROXY,但刚开始不知道的时候 go mod download 一直超时。还有 vendor 目录要不要提交到 git 里,团队里意见也不统一。

编译是真省心:go build 一个二进制出来,往服务器上一扔就能跑,不用装 runtime。但交叉编译如果不记得配 GOOSGOARCH,在本机编译好的二进制扔到服务器上直接 "exec format error"——这个坑我也不止一次踩过。

总结

半年用下来,Go 给我的感觉是:学起来快,写好难。很多设计哲学跟 Python 完全相反——Python 说"人生苦短,我用 Python",Go 说"少即是多,显式优于隐式"。谈不上哪个更好,只能说场景不同。

现在我 Python 和 Go 都在用:快速原型和数据分析还是 Python 顺手,性能敏感的后端服务和 CLI 工具就上 Go。两个互补着用,比只用一个顺手多了。

踩过这些坑之后自然就会注意了,倒不是什么大问题。新手转 Go 的话记住几点:map 一定用 make,goroutine 一定考虑退出,err 一定不要忽略——把这几条刻在脑子里,能少踩一半的坑。

评论一下?

OωO
取消