文章目录

Go语言iter包的Pull迭代器与Push迭代器

发布于 2026-04-23 09:27:03 · 浏览 6 次 · 评论 0 条

Go语言从1.23版本开始,在标准库中引入了 iter 包,正式确立了迭代器模式。在 iter 包中,迭代器主要分为两种形式:Push(推送)迭代器Pull(拉取)迭代器。理解两者的区别与转换是掌握Go新特性的关键。


1. 理解Push迭代器

Push迭代器是Go语言中最基础的迭代器形式,也被称作 Seq(Sequence)。它的本质是一个函数,接收一个“yield”回调函数作为参数。迭代器负责推送数据给yield函数。

定义type Seq[V any] func(yield func(V) bool)

在这种模式下,迭代器本身控制循环的节奏。当你使用 for range 遍历一个 Seq 时,实际上是 range 在不断调用你传入的函数,直到该函数停止返回 true 或执行完毕。

编写一个简单的Push迭代器:

  1. 定义一个函数,其签名为 func(yield func(int) bool)
  2. 在函数内部,开启一个循环(例如 for 循环)。
  3. 在循环体内,调用 yield(值) 将数据传出。
  4. 检查 yield 的返回值,如果为 false中断 循环。
package main

import "fmt"

// ListNumbers 是一个典型的Push迭代器
func ListNumbers(yield func(int) bool) {
    for i := 1; i <= 3; i++ {
        // 将值推给 yield,如果 yield 返回 false,则停止
        if !yield(i) {
            return
        }
    }
}

func main() {
    // 使用 for range 遍历 Push 迭代器
    for n := range ListNumbers {
        fmt.Println("收到:", n)
    }
}

2. 使用Pull迭代器

Push迭代器虽然简单,但有一个限制:它必须一次性运行完毕,无法在中间暂停并根据外部逻辑随时获取下一个值。当你需要手动控制迭代的进度(例如:一次取一个,处理一些逻辑,再取下一个)时,就需要使用 Pull迭代器

Pull迭代器通过 iter.Pull 函数将一个 Push迭代器转换为一对函数:

  • next():用于手动获取下一个值。
  • stop():用于停止迭代并释放资源。

转换与使用步骤:

  1. 准备一个现有的Push迭代器(如上一节的 ListNumbers)。
  2. 调用 iter.Pull(ListNumbers),获取 next, stop 两个函数。
  3. 执行 next() 获取值和状态。它返回 value, ok
    • oktrue:表示获取到了有效值。
    • okfalse:表示迭代结束。
  4. 根据业务逻辑判断是否 继续调用 next()
  5. 必须调用 stop() 来结束迭代(即使在迭代结束后也建议调用,以确保清理资源)。
package main

import (
    "fmt"
    "iter"
)

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

func main() {
    // 1. 转换为 Pull 迭代器
    next, stop := iter.Pull(ListNumbers)
    // 4. 确保最终停止迭代器
    defer stop()

    fmt.Println("准备开始拉取数据...")

    // 2. 手动调用 next 获取数据
    for {
        val, ok := next()
        if !ok {
            // 3. 如果 ok 为 false,说明没有数据了
            break
        }

        fmt.Printf("手动拉取到: %d\n", val)

        // 在这里可以插入任意复杂的逻辑,而不像 range 那样被束缚
        if val == 2 {
            fmt.Println("拉取到 2,准备结束")
            break 
        }
    }
}

3. 处理键值对

除了单值序列 Seq[V],Go还提供了双值序列 Seq2[K, V],常用于遍历Map或同时返回索引和值的场景。对应的Pull转换函数是 iter.Pull2

使用 iter.Pull2 的逻辑与 iter.Pull 几乎一致:

  1. 定义或获取一个 Seq2 类型的迭代器(例如遍历Map)。
  2. 调用 iter.Pull2(seq2) 获取 nextstop
  3. 此时的 next 函数签名为 func() (K, V, bool)
  4. 循环调用 next(),解包获取 key, value, ok
package main

import (
    "fmt"
    "iter"
)

// Pairs 返回一个包含键值对的迭代器
func Pairs(yield func(string, int) bool) {
    yield("Apple", 10)
    yield("Banana", 20)
    yield("Cherry", 30)
}

func main() {
    // 转换键值对迭代器
    next, stop := iter.Pull2(Pairs)
    defer stop()

    for {
        k, v, ok := next()
        if !ok {
            break
        }
        fmt.Printf("Key: %s, Value: %d\n", k, v)
    }
}

4. 迭代器模式对比与选择

在开发过程中,如何选择这两种迭代器取决于你对流程控制权的需求。下表总结了它们的区别和使用场景。

特性 Push 迭代器 (Seq) Pull 迭代器
控制权 迭代器内部控制循环,外部被动接收 外部调用者控制何时获取下一个值
语法形式 func(yield func(V) bool) next() (V, bool), stop()
主要用途 标准遍历,for range 循环 需要手动步进、复杂逻辑判断或异步处理
转换方式 原生形式,无需转换 通过 iter.Pull / iter.Pull2 转换
资源释放 通常随循环结束自动结束 必须显式调用 stop() 函数

核心建议

  • 如果只是简单的遍历数据集,直接使用 for range 配合 Push迭代器 即可,这是最简洁的方式。
  • 如果需要在遍历过程中暂停、等待网络请求、或者根据复杂的非局部条件决定是否继续获取下一个元素,请使用 iter.Pull 转换为 Pull迭代器

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文