← 返回 Day 22 正文

Day 22 · 图解

Day 022 — 生成器原理图解

1. 生成器执行流程(yield 暂停/恢复)

生成器生命周期:

    创建生成器对象             next(gen) 调用          StopIteration
         │                        │                      │
         ▼                        ▼                      ▼
    ┌─────────┐            ┌─────────────┐          ┌─────────┐
    │ 冷冻态  │    next()  │   执行中     │  return  │ 结束态  │
    │ 不执行  │ ─────────→ │  ┌───────┐  │ ───────→ │ 关闭    │
    │ 代码    │            │  │yield  │  │          │         │
    └─────────┘            │  │暂停   │  │          └─────────┘
                           │  │保存栈 │  │
                           │  └───────┘  │
                           └──────┬──────┘
                                  │ next() 调用
                                  ▼
                           ┌─────────────┐
                           │   从断点     │
                           │   继续执行    │
                           │  ┌───────┐  │
                           │  │yield  │  │
                           │  │...    │  │
                           │  └───────┘  │
                           └─────────────┘

2. yield 状态机模型

代码:
    def my_gen():
        print("start")
        yield 1
        print("middle")
        yield 2
        print("end")

编译后内部状态机:

    ┌─────────────────────────────────────────────────────┐
    │                 生成器状态机                          │
    ├──────────┬──────────────────┬──────────────────────┤
    │ 状态编号 │   执行代码        │   下一状态            │
    ├──────────┼──────────────────┼──────────────────────┤
    │    0     │ print("start")   │ 暂停 → 状态 1        │
    │          │ yield 1          │ 返回值 1              │
    ├──────────┼──────────────────┼──────────────────────┤
    │    1     │ print("middle")  │ 暂停 → 状态 2        │
    │          │ yield 2          │ 返回值 2              │
    ├──────────┼──────────────────┼──────────────────────┤
    │    2     │ print("end")     │                      │
    │          │ return           │ StopIteration        │
    └──────────┴──────────────────┴──────────────────────┘

    执行流程:

    next() ──→ 状态 0 ──→ yield 1 ──→ next() ──→ 状态 1 ──→ yield 2
                  ↑                                             │
                  │                   next()                    │
                  └─────────────────────────────────────────────┘
                                                                 │
                                                            return ──→ StopIteration

3. send/throw/close 交互

调用者                            生成器
  │                                │
  │  next(gen)                     │
  │ ──────────────────────────────→│  进入生成器,执行到 yield
  │                                │  print("...")
  │                                │  yield value
  │ ←──────────────────────────────│  返回 value,暂停
  │                                │
  │  gen.send(data)                │
  │ ──────────────────────────────→│  yield 表达式求值为 data
  │                                │  继续执行到下一个 yield
  │                                │  yield value2
  │ ←──────────────────────────────│  返回 value2,暂停
  │                                │
  │  gen.throw(ValueError)         │
  │ ──────────────────────────────→│  在 yield 处抛出 ValueError
  │                                │  try/except 处理或传播
  │                                │  如果处理了→继续 yield
  │ ←──────────────────────────────│  或异常穿透到调用者
  │                                │
  │  gen.close()                   │
  │ ──────────────────────────────→│  在 yield 处抛出 GeneratorExit
  │                                │  finally 执行清理
  │                                │  生成器关闭

4. yield from 双向通道

调用者 ─── send(value) ──→ 主生成器 ── yield from ──→ 子生成器
                              │                           │
                              │                           │
  调用者 ←── return_val ←── yield from ←── yield val ←───│

  实际上 yield from 打通了一条双向通道:
  - 调用者的 send() 直接传到子生成器的 yield 表达式
  - 子生成器的 yield 值直接返回到调用者
  - 子生成器的 return 值作为 yield from 表达式的值

  示例流程:
  def main():
      result = yield from sub()
      # result = "子返回的值"

  def sub():
      x = yield "准备就绪"
      # x = 调用者 send("数据")
      return "完成"

  调用者          main()            sub()
    │               │                │
    │ next()        │                │
    │──────────────→│                │
    │               │───────────────→│ (进入 sub)
    │               │                │ yield "准备就绪"
    │               │←───────────────│
    │←──────────────│
    │               │                │
    │ send("数据")  │                │
    │──────────────→│                │
    │               │───────────────→│ (x = "数据")
    │               │                │ return "完成"
    │               │←───────────────│
    │               │ result = "完成" │
    │               │ return         │
    │←──────────────│

5. 生成器 vs 迭代器 vs 列表 内存对比

生成 1 到 1000000 的平方数:

列表方式 (内存 O(n)):
    [1, 4, 9, 16, ..., 10^12]
    ┌────────────────────────────────────────────┐
    │ 所有值同时存在内存中                         │
    │ 约 28MB 内存                                │
    │ 随时可访问任意元素                           │
    └────────────────────────────────────────────┘

迭代器方式 (内存 O(1)):
    <list_iterator object>
    ┌────────────────────────────────────────────┐
    │ 几乎不占内存                                │
    │ 逐个产生值,用完即弃                         │
    │ 只能遍历一次                                │
    └────────────────────────────────────────────┘

生成器方式 (内存 O(1)):
    <generator object>
    ┌────────────────────────────────────────────┐
    │ 几乎不占内存                                │
    │ 惰性求值,按需计算                           │
    │ 代码最简洁                                  │
    │ 支持 send/throw/close                       │
    └────────────────────────────────────────────┘

6. 生成器在数据管道中的应用

数据流:
    ┌──────┐    ┌──────┐    ┌──────┐    ┌──────┐
    │ 读取  │───→│ 过滤  │───→│ 转换  │───→│ 输出  │
    │ 生成器│    │ 生成器│    │ 生成器│    │ 使用  │
    └──────┘    └──────┘    └──────┘    └──────┘
         │          │          │          │
         │          │          │          │
         ▼          ▼          ▼          ▼
     逐行读取    条件筛选   数据转换    汇总/存储
     内存: 1行   内存: 1行  内存: 1行    内存: 1行

    整个管道任何时候都只有 1 行数据在内存中!

7. 协程 vs 子例程 vs 生成器

子例程(普通函数)

    A() 调用 ──→ 进入 B()
                  B() 从头到尾执行
                  B() 返回 ──→ A() 继续
    A() 结束

    特点: 一次进入,一次退出

生成器(带 yield)

    调用 gen() ──→ 创建生成器对象
    next(gen) ──→ 进入函数体
                   执行到 yield ──→ 暂停并返回值
    next(gen) ──→ 从暂停处继续
                   执行到 yield ──→ 暂停
    next(gen) ──→ 从暂停处继续
                   函数返回 ──→ StopIteration

    特点: 多次进入退出,状态保持

协程(带 send/throw)

    next(coro) ──→ 启动
    coro.send(x) ──→ 发送数据并继续
                      处理数据
                      yield result ──→ 返回结果并暂停
    coro.send(y) ──→ 再次发送

    特点: 双向通信,数据可以流入流出

8. 生成器属性结构

classDiagram class Generator { -gi_frame : FrameObject -gi_code : CodeObject -gi_running : bool -gi_yieldfrom : Generator | None +__next__() : Any +send(value) : Any +throw(exc_type, ...) : Any +close() : None } class FrameObject { -f_back : FrameObject -f_locals : dict -f_lineno : int -f_lasti : int } class CodeObject { -co_name : str -co_filename : str -co_code : bytes -co_varnames : tuple } Generator --> FrameObject : gi_frame Generator --> CodeObject : gi_code FrameObject --> FrameObject : f_back (linked list)
classDiagram
    class Generator {
        -gi_frame : FrameObject
        -gi_code : CodeObject
        -gi_running : bool
        -gi_yieldfrom : Generator | None
        +__next__() : Any
        +send(value) : Any
        +throw(exc_type, ...) : Any
        +close() : None
    }

    class FrameObject {
        -f_back : FrameObject
        -f_locals : dict
        -f_lineno : int
        -f_lasti : int
    }

    class CodeObject {
        -co_name : str
        -co_filename : str
        -co_code : bytes
        -co_varnames : tuple
    }

    Generator --> FrameObject : gi_frame
    Generator --> CodeObject : gi_code
    FrameObject --> FrameObject : f_back (linked list)

9. 生成器生命周期(Mermaid 序列图)

sequenceDiagram participant Caller as 调用者 participant Generator as 生成器对象 participant Frame as 栈帧 Caller->>Generator: gen = my_gen() Note over Generator,Frame: 创建生成器<br/>栈帧已分配但冻结 Caller->>Generator: next(gen) Generator->>Frame: 激活栈帧 Note over Frame: 执行到 yield Frame-->>Generator: 冻结栈帧 Generator-->>Caller: yield 值 Caller->>Generator: next(gen) Generator->>Frame: 恢复栈帧 Note over Frame: 从 yield 处继续 Frame-->>Generator: 冻结栈帧 Generator-->>Caller: yield 值 Caller->>Generator: next(gen) Generator->>Frame: 恢复栈帧 Note over Frame: 执行到 return Frame-->>Generator: 销毁栈帧 Generator-->>Caller: StopIteration
sequenceDiagram
    participant Caller as 调用者
    participant Generator as 生成器对象
    participant Frame as 栈帧

    Caller->>Generator: gen = my_gen()
    Note over Generator,Frame: 创建生成器<br/>栈帧已分配但冻结

    Caller->>Generator: next(gen)
    Generator->>Frame: 激活栈帧
    Note over Frame: 执行到 yield
    Frame-->>Generator: 冻结栈帧
    Generator-->>Caller: yield 值

    Caller->>Generator: next(gen)
    Generator->>Frame: 恢复栈帧
    Note over Frame: 从 yield 处继续
    Frame-->>Generator: 冻结栈帧
    Generator-->>Caller: yield 值

    Caller->>Generator: next(gen)
    Generator->>Frame: 恢复栈帧
    Note over Frame: 执行到 return
    Frame-->>Generator: 销毁栈帧
    Generator-->>Caller: StopIteration

10. 生成器表达式 AST 结构

生成器表达式: (x**2 for x in range(10))

         GeneratorExp
         /          \
     Name('x')    comp_iter
                    |
                 comp_for
                /        \
          Name('x')   comp_iter
                       |
                    call_function
                    /           \
              Name('range')  Num(10)