golang 中类似Python yield的实现

最后发布时间:2026-02-08 18:10:40 浏览量:

这个问题问得非常对位
一句话先放这:

Go 里没有 yield 关键字,但“yield 的能力 是 Go 的一等公民”。

只是 实现方式完全不同,而且更偏工程、并发友好。

我按「语义 → 实现 → 对照 → 什么时候用哪个」给你讲。

一、先对齐语义:Python yield 到底是什么

def gen():
    for i in range(3):
        yield i

本质能力有三点:

  1. 暂停 / 恢复执行
  2. 保存局部状态
  3. 按需产出数据(pull-based)

二、Go 的“等价物”是什么?

核心答案:

channel + goroutine = Python yield

但注意:
Go 是 并发 push,Python 是 单线程 pull

三、最经典的 Go 写法(99% 的场景)

Python

def gen():
    for i in range(3):
        yield i

for v in gen():
    print(v)

Go(等价)

func gen() <-chan int {
    ch := make(chan int)
    go func() {
        defer close(ch)
        for i := 0; i < 3; i++ {
            ch <- i
        }
    }()
    return ch
}

for v := range gen() {
    fmt.Println(v)
}

对照关系

PythonGo
yield vch <- v
generatorgoroutine
StopIterationclose(ch)
for v in genfor v := range ch

四、这是“语义完全等价”吗?

不完全,但更强

能力Python yieldGo channel
保存状态
惰性⚠️(依赖调度)
backpressure⚠️
并发
多消费者
select / cancel

👉 Go 的方案是 工业级 yield

五、你如果想要“更像 yield 的 pull 模式”

用函数 + 回调(不推荐)

func gen(yield func(int) bool) {
    for i := 0; i < 3; i++ {
        if !yield(i) {
            return
        }
    }
}

使用:

gen(func(v int) bool {
    fmt.Println(v)
    return true
})

这在语义上更接近 Python,但:

  • ❌ 不自然
  • ❌ 不好组合
  • ❌ 不好中断

六、Go 1.23+:iter.Seq(⚠️重要)

Go 官方已经正面承认 yield 的价值

新增标准库(实验 → 正式中)

import "iter"

func gen() iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 0; i < 3; i++ {
            if !yield(i) {
                return
            }
        }
    }
}

使用方式:

for v := range gen() {
    fmt.Println(v)
}

这已经是:

“官方 yield”

但目前:

  • 生态还在追
  • channel 依然是主流

七、工程实践中怎么选?(很关键)

✅ 用 channel 的时候

  • 流式 IO
  • WebSocket / SSE
  • ASR / TTS / LLM streaming
  • 多 goroutine 协作
  • 需要 cancel / timeout

👉 你的项目 100% 该用 channel

✅ 用 iter.Seq 的时候

  • 单线程数据处理
  • 类似 Python 的 map/filter
  • 惰性集合
  • 算法 / DSL / pipeline

八、把 Python 音频 yield 翻成 Go(你熟的场景)

Python(你之前的)

while running:
    frame = q.get()
    yield frame

Go(工程级)

func stream(ctx context.Context, q <-chan []byte) <-chan []byte {
    out := make(chan []byte)

    go func() {
        defer close(out)
        for {
            select {
            case <-ctx.Done():
                return
            case frame, ok := <-q:
                if !ok {
                    return
                }
                out <- frame
            }
        }
    }()

    return out
}

✔️ cancel
✔️ backpressure
✔️ graceful shutdown

九、一句工程师总结(送你)

Python 的 yield 是“语言级控制流”
Go 的 channel 是“系统级数据流”

你从 Python 流式代码转 Go,
不是少了 yield,而是升级了模型

如果你愿意,我可以下一步帮你:

  • 把你那段 Python 语音流完整翻成 Go
  • 对比 Go channel vs async generator 的性能
  • 深挖 iter.Seq 的设计动机(为什么 Go 现在才加)

你想从哪个继续?