Python以运行时栈帧保存生成器状态 消除迭代器类模板代码

【python】生成器是什么?怎么用?能干啥?一期视频解决你所有疑问! — 码农高天

码农高天

Python在编译期将包含yield的函数打上生成器标签,运行时直接依靠挂起栈帧而非类实例变量保存执行进度,使开发者无需编写迭代器类即可借助send实现双向通信。

AI-assisted summary of 【python】生成器是什么?怎么用?能干啥?一期视频解决你所有疑问! — original source. Check the original for full context.

Source published:

Key ideas

  • Python 编译器在发现函数内部包含 yield 关键字时会自动打上标签,使其定义成为生成器函数;调用时不会直接执行函数体,而是返回一个生成器对象。 (0:00)
  • 生成器与传统迭代器的核心区别在于状态存储机制:迭代器将状态维护在类实例的变量中,而生成器将执行进度与局部变量直接保存在运行时的栈帧(Frame)中。 (5:00)
  • 生成器函数中的 return 语句在语义上等价于抛出 StopIteration 异常,其返回值无法通过普通 for 循环或 next 直接读取,必须显式捕获异常对象才能获取。 (5:00)
  • 生成器支持通过 send 方法实现双向通信;yield 不仅能产出值,还能作为表达式接收外部传入的数据,且 next(g) 在底层机制上完全等价于 g.send(None)。 (7:22)

Detailed notes

## Background 该视频由 YouTube 频道“码农高天”于 2023 年 10 月 10 日发布。视频简介指出,本期主要系统性讲解 Python 中常用且重要的生成器机制,以此填补此前系列视频中提及的相关技术坑点。

## TL;DR 本期视频深入解析了 Python 生成器的核心概念与底层运作原理,指出生成器本质上是一种特殊的迭代器。作者辨析了生成器函数与生成器对象的区别,揭示了 Python 编译期通过 yield 关键字标记函数,并在运行时借助执行栈帧(Frame)自动挂起与恢复执行状态的底层字节码机制。此外,视频通过重构链表遍历对比了生成器相较传统迭代器类的简洁优势,并详细演示了如何利用 send 方法实现外部与生成器内部的双向数据通信。

## Atmosphere & style 视频整体呈现出严谨、清晰且循循善诱的技术讲座风格,极具硬核工程师的专业质感。作者以从浅入深、层层递进的逻辑展开教学,既有生动的代码对比,又直切底层字节码与执行栈帧的运作细节,让观众感受到一种豁然开朗的透彻感。整场讲解语调平稳笃定,在拆解复杂语法与通信机制时显得游刃有余、条理分明。

## Bulletin - [00:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=0s) Python 编译器在发现函数内部包含 yield 关键字时会自动打上标签,使其定义成为生成器函数;调用时不会直接执行函数体,而是返回一个生成器对象。 - [05:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=300s) 生成器与传统迭代器的核心区别在于状态存储机制:迭代器将状态维护在类实例的变量中,而生成器将执行进度与局部变量直接保存在运行时的栈帧(Frame)中。 - [05:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=300s) 生成器函数中的 return 语句在语义上等价于抛出 StopIteration 异常,其返回值无法通过普通 for 循环或 next 直接读取,必须显式捕获异常对象才能获取。 - [07:22](https://www.youtube.com/watch?v=tWOD43HQIRA&t=442s) 生成器支持通过 send 方法实现双向通信;yield 不仅能产出值,还能作为表达式接收外部传入的数据,且 next(g) 在底层机制上完全等价于 g.send(None)。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=0s) 生成器本质上是一种特殊的迭代器,同样支持 for 循环遍历以及通过 next() 函数逐步提取数据。 - [00:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=0s) 初学者极易混淆生成器函数与生成器对象:包含 yield 的 def 函数是生成器函数,调用该函数仅返回生成器对象而不执行具体代码。 - [00:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=0s) 生成器函数体内的逻辑仅在外部对其调用 next() 时才正式启动执行。 - [00:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=0s) 在底层字节码层面,遇到 YIELD_VALUE 时 Python 会将栈顶值存入返回值、记录栈深度并暂停当前栈帧退出,后续调用 next() 时直接在暂停处恢复运行。 - [00:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=0s) 在生成器函数内部,编写 return 等价于 raise StopIteration,其返回值不会在正常迭代中被打印输出。 - [05:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=300s) 若要获取生成器内部 return 所携带的具体数值,调用方必须通过 try-except 显式捕获 StopIteration 异常对象。 - [05:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=300s) 传统自定义迭代器类需要手动编写 __iter__、__next__ 并显式维护内部状态指针,模板代码相对冗长。 - [05:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=300s) 生成器自动利用 Python 栈帧记住代码执行阶段与局部变量,极大精简了诸如链表遍历等场景下的代码量。 - [07:22](https://www.youtube.com/watch?v=tWOD43HQIRA&t=442s) yield 既可以是一条独立的产出语句,也可以作为表达式在求值后将外部传入的值赋给局部变量(如 temp = yield num)。 - [07:22](https://www.youtube.com/watch?v=tWOD43HQIRA&t=442s) 生成器的底层调用机制中,next(g) 的行为与 g.send(None) 完全一致。 - [07:22](https://www.youtube.com/watch?v=tWOD43HQIRA&t=442s) 如果生成器函数中的 yield 没有赋值给变量,外部通过 send 传入的任何数据都会被静默丢弃,不会引发报错。 - [07:22](https://www.youtube.com/watch?v=tWOD43HQIRA&t=442s) send 方法为外部与生成器之间提供了交流与动态改变内部状态的机制,是 Python 协程和双向数据传递的重要底层基础。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=0s) 生成器与迭代器的关系及生成器函数与对象的本质区别

在深入探讨生成器之前,码农高天强烈建议观众先看完前一期关于迭代器的内容,因为“生成器就是一种特殊的迭代器,当你理解了什么是迭代器之后,你理解生成器就比较容易了”。生成器与迭代器有很多共性:可以通过 `for i in g` 这种循环方式遍历出数据(如运行示例打印出 5、4、3、2、1),也可以使用 `next()` 函数先取出第一个值,再配合循环输出后续的值。

然而,初学者极易混淆两个概念:“生成器函数”与“生成器对象”。传统的迭代器是一个正儿八经的 `class`,实例化后得到一个迭代器对象,语义清晰不易犯错;而生成器函数的定义与普通函数几乎一模一样,都是 `def gen(...)` 格式。很多初学者看到里面写了 `return`,会误以为调用 `gen(5)` 会直接返回一个 `None` 并赋给变量 `g`。码农高天指出这是 Python 编译期的一个精妙机制:“当它发现一个函数的定义里面有 `yield` 这个关键词的时候,他就不会把这个函数当成一个普通的函数来处理。Python 会给这个函数打上一个标签,然后说这是一个生成器函数。”

```mermaid flowchart TD accTitle: Generator Function Compilation and Execution Flow accDescr: Shows how Python compiles a function with yield and executes next calls through frame pausing and resuming. subgraph DefPhase ["1. 编译与创建阶段"] A["函数定义中包含 yield 关键字"] -->|"编译期打标签"| B["生成器函数 (generator function)"] B -->|"调用 gen(5) 不执行函数体"| C["返回生成器对象 (generator object)"] end

subgraph ExecPhase ["2. 运行与暂停阶段"] C -->|"首次调用 next(g)"| D["执行 Bytecode 到 YIELD_VALUE"] D -->|"栈顶值存入 return_value"| E["暂停 Frame,记录栈深度并退出"] E -->|"再次调用 next(g)"| F["从上次暂停的 yield 处继续恢复执行"] F -->|"执行到 return 或自然结束"| G["抛出 StopIteration 异常"] end ```

- **生成器函数与对象**:`def gen(...)` 称为生成器函数;调用 `g = gen(5)` 时并不会直接执行函数本体,而是返回一个生成器对象 `g`。 - **运行机制**:只有在对生成器对象调用 `next()` 时,函数本体才真正开始执行。 - **底层字节码(Bytecode)**:当遇到 `YIELD_VALUE` 字节码时,Python 会将栈顶值取出存入返回值,将当前栈帧(frame)标记为尚未结束,保存当前的调用栈深度,然后跳转退出(`goto exiting` 返回值)。下次再次调用 `next()` 时,会在暂停的位置恢复执行。 - **`return` 的语义**:“在生成器函数里面,所以 `return` 等价于 `raise StopIteration`。” 无论 `return` 是否携带具体数值,在常规的 `next()` 调用或 `for` 循环中都不会被直接输出;唯有 `yield` 的值会被捕获并输出。

[05:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=300s) 状态保存原理对比与链表重构示例

如果想要获取生成器中 `return` 的值,不能通过常规的遍历或直接读取,而是必须显式去捕获 `StopIteration` 异常对象中的返回值。比如在生成器末尾写上 `return 100`,常规的 `for` 循环完全不会打印出任何与 100 相关的结果。

从使用者的角度来看,生成器和迭代器在使用上几乎毫无差别,但底层保存状态的方式有根本不同:“我们正常定义的迭代器也就是 iterator,它是把迭代时候的状态保存在一个对象里的;而生成器也就是 generator,它实际上是把这个状态保存在了 frame 里,就它的状态是函数运行到哪一步了,而不是通过一个变量来记录我现在的状态。”

```mermaid flowchart LR accTitle: State Storage Comparison Between Iterator and Generator accDescr: Contrasts class-based state stored in instance variables with generator state stored in the execution frame. subgraph ClassIterator ["传统迭代器 (Iterator)"] direction TB A1["自定义迭代器类"] --> A2["实例内部变量 (self.node / self.idx)"] A2 --> A3["手动更新维护状态"] end

subgraph GenIterator ["生成器 (Generator)"] direction TB B1["包含 yield 的生成器函数"] --> B2["执行栈帧 (Frame Context)"] B2 --> B3["局部变量与代码执行进度自动挂起/恢复"] end ```

码农高天引用了上期视频中的链表遍历示例进行对比说明。在传统迭代器中,需要单独写一个迭代器类并在内部维护指针状态;而使用生成器时,只需将 `__iter__` 方法写成一个生成器函数:从自身节点开始 `yield` 出当前节点,然后将指针移动到下一个节点,循环直至当前节点为 `None`。

| 特性维度 | 传统迭代器类 (Iterator Class) | 生成器 (Generator) | |:--- |:--- |:--- | | **状态存储位置** | 保存在具体类的实例对象变量中 | 保存在 Python 运行时的栈帧(Frame)中 | | **实现复杂度** | 需完整编写类定义、`__iter__` 与 `__next__` | 仅需在函数中配合循环使用 `yield` 关键字 | | **代码量** | 相对冗长,需显式维护指针与边界判定 | 代码极度精简,逻辑更加直观清晰 | | **退出控制** | 手动显式 `raise StopIteration` | 执行到普通 `return` 或代码结尾时自动抛出异常 |

- **简洁性优势**:用生成器重写链表的 `__iter__` 之后,无需创建配套的迭代器辅助类,运行结果完全一致,但在简单场景下能够大幅削减模板代码量。 - **局部变量的保留**:栈帧自动记住了局部变量以及代码执行到的位置,省去了手动管理状态变量的负担。

[07:22](https://www.youtube.com/watch?v=tWOD43HQIRA&t=442s) 高级特性 `send` 方法与双向通信机制

在基础特性之外,生成器还具备一个更为高级的用法——`send` 方法。“`send` 是什么意思呢?`send` 就是在这个生成器 `yield` 之后,把这个 `yield` 什么东西变成一个值,然后这个值还可以继续赋给这个生成器函数里的其他变量。”

通常情况下,`yield num` 仅仅是一条语句,但它也可以作为一个表达式被赋值,例如 `temp = yield num`。执行时,生成器会先将当前的值 `yield` 出去(无论外部是通过 `next()` 还是 `send()` 触发),随后代码在此处暂停,等待外部通过 `send()` 传递进来的值;这个值就像是让 `yield` 表达式完成了一次求值求出来的结果。

```mermaid flowchart TD accTitle: Bidirectional Communication via Generator send Method accDescr: Illustrates how values are yielded out and new values are sent into a generator to modify internal variables. A["外部首次调用 next(g)"] --> B["函数执行到 temp = yield num,输出值 5 并暂停"] B --> C["外部调用 g.send(10)"] C --> D["yield 表达式被求值为 10,并赋值给 temp"] D --> E["检测到 temp 不是 None,将 num 重置为 10"] E --> F["执行 num -= 1 后循环回到 yield,输出值 9 并再次暂停"] F --> G["外部通过 for 循环执行,等价于连续调用 g.send(None)"] G --> H["temp 被赋予 None,内部数值正常递减输出 8 到 1"] ```

在修改后的程序中,首次调用 `first = next(g)` 先取出了初始值 5;接着调用 `g.send(10)`,传入的 10 作为 `yield` 表达式的返回值赋给了 `temp`。生成器内部检测到 `temp` 不是 `None`,便把主循环变量 `num` 重新赋值为 10,随后执行减 1 操作,因此紧接着产出的值为 9。

- **`next()` 与 `send(None)` 的等价性**:在底层机制上,“对于生成器来说,`next(g)` 和 `g.send(None)` 是一模一样的。”在随后的 `for i in g` 循环中,由于循环底层不断调用的实质等同于 `g.send(None)`,变量 `temp` 每次都会被赋为 `None`,不会再触发重置逻辑,数值便从 8 一路按步长递减输出到 1。 - **无赋值时的静默丢弃**:如果生成器函数中的 `yield num` 只是作为单条独立语句存在,没有赋值给任何变量,那么无论外部 `send` 什么数值,该值都会被静默丢弃,不会影响状态,也不会使生成器崩溃。此时 `send` 任意值和使用 `next()` 表现完全相同。 - **核心定位与价值**:“`send` 函数只是给你一个机制,让你可以去和你的 generator 做沟通和交流,可以改变他的一些状态。”它为 Python 协程和生成器的双向数据传递提供了底层的语言支撑。

## Notable quotes > [05:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=300s) 我们正常定义的迭代器也就是 iterator,它是把迭代时候的状态保存在一个对象里的;而生成器也就是 generator,它实际上是把这个状态保存在了 frame 里 — 码农高天

> [00:00](https://www.youtube.com/watch?v=tWOD43HQIRA&t=0s) 生成器就是一种特殊的迭代器,当你理解了什么是迭代器之后,你理解生成器就比较容易了 — 码农高天

> [07:22](https://www.youtube.com/watch?v=tWOD43HQIRA&t=442s) send 函数只是给你一个机制,让你可以去和你的 generator 做沟通和交流,可以改变他的一些状态。 — 码农高天

## Mentioned Python, 码农高天

## Video description 这期视频讲一下生成器,在python中还是比较常用的。也是填上了之前挖了好多次的坑。

[Source](https://www.youtube.com/watch?v=tWOD43HQIRA)

中文简报

Python以运行时栈帧保存生成器状态 消除迭代器类模板代码 Python在编译期将包含yield的函数打上生成器标签,运行时直接依靠挂起栈帧而非类实例变量保存执行进度,使开发者无需编写迭代器类即可借助send实现双向通信。 本期视频深入解析了 Python 生成器的核心概念与底层运作原理,指出生成器本质上是一种特殊的迭代器。作者辨析了生成器函数与生成器对象的区别,揭示了 Python 编译期通过 yield 关键字标记函数,并在运行时借助执行栈帧(Frame)自动挂起与恢复执行状态的底层字节码机制。此外,视频通过重构链表遍历对比了生成器相较传统迭代器类的简洁优势,并详细演示了如何利用 send 方法实现外部与生成器内部的双向数据通信。 Python 编译器在发现函数内部包含 yield 关键字时会自动打上标签,使其定义成为生成器函数;调用时不会直接执行函数体,而是返回一个生成器对象。 (0:00) 生成器与传统迭代器的核心区别在于状态存储机制:迭代器将状态维护在类实例的变量中,而生成器将执行进度与局部变量直接保存在运行时的栈帧(Frame)中。 (5:00) 生成器函数中的 return 语句在语义上等价于抛出 StopIteration 异常,其返回值无法通过普通 for 循环或 next 直接读取,必须显式捕获异常对象才能获取。 (5:00) 生成器支持通过 send 方法实现双向通信;yield 不仅能产出值,还能作为表达式接收外部传入的数据,且 next(g) 在底层机制上完全等价于 g.send(None)。 (7:22)

Reading summary

码农高天在讲解中指出,生成器本质上是一种特殊的迭代器,通过在编译期识别 `yield` 关键字来创建生成器对象,并将运行状态保存在栈帧中,同时借助 `send` 机制支持双向通信与状态修改。 生成器函数的编译识别与字节码执行机制 (0:00) 传统迭代器依赖独立的类与显式实例化,而生成器使用与普通函数相同的 `def` 语法定义。Python 在编译期检测到函数体内存在 `yield` 关键字时,会为其打上生成器函数的标签。调用此类函数时,系统不会直接执行函数体代码,而是返回一个生成器对象。只有在外部对其调用 `next()` 时,函数体才真正开始运行。 在底层执行层面,生成器遇到 `YIELD_VALUE` 字节码时,会将栈顶值存入返回值并暂停当前栈帧(frame),记录当前的调用栈深度后退出。再次调用 `next()` 时,代码会从上次暂停的位置恢复执行。在生成器函数内部,`return` 的语义等价于 `raise StopIteration`,无论其是否携带具体数值,常规的 `for` 循环或 `next()` 都不会直接输出该返回值,必须显式捕获异常对象才能获取。 栈帧保存状态相较传统迭代器的精简优势 (5:00) 生成器与传统迭代器在外部使用形式上高度一致,均支持 `for` 循环与 `next()` 遍历,但二者存储状态的底层机制存在本质差异。传统的类迭代器需要开发者在实例对象中显式维护内部变量(如链表遍历中的指针或索引);而生成器直接将运行状态托管给 Python 的运行时栈帧,自动记住代码执行进度和局部变量。 在重构链表遍历的示例中,若使用生成器,只需将 `__iter__` 方法实现为包含 `yield` 的函数,在循环中产出当前节点并推进指针,直至节点为空。相较于传统迭代器需要单独编写类、维护 `__next__` 并手动抛出 `StopIteration`,生成器消除了大量的模板代码与边界判定逻辑,使代码更为精简直观。 send方法带来的双向通信与动态状态调整 (7:22) 除了向外单向产出数据,生成器还支持通过 `send` 方法实现外部与生成器内部的双向数据传递。当代码写为 `temp = yield num` 时,`yield` 兼具产出值与表达式接收值的双重功能。首次调用 `next()` 或 `send()` 会使生成器暂停在 `yield` 处,随后外部调用 `g.send(value)` 会将传入的值赋给 `temp` 并恢复执行。 例如在初始值为 5 的倒计时示例中,首次获取 5 后调用 `g.send(10)`,生成器内部检测到输入值并将主变量重置为 10,随后递减产出 9。在底层逻辑上,`next(g)` 与 `g.send(None)` 完全等价;在常规 `for` 循环中,由于持续传入 `None`,重置逻辑不会被触发。若 `yield` 仅作为独立语句而未赋值给变量,外部通过 `send` 传入的值会被静默丢弃,表现与 `next()` 一致。 生成器通过栈帧保留状态简化了迭代器的编写流程,并通过 `send` 方法提供了改变内部状态与双向通信的底层语言支撑,这也是 Python 协程体系的重要基础机制。

Source quotes

[5:00] 码农高天: 我们正常定义的迭代器也就是 iterator,它是把迭代时候的状态保存在一个对象里的;而生成器也就是 generator,它实际上是把这个状态保存在了 frame 里 [0:00] 码农高天: 生成器就是一种特殊的迭代器,当你理解了什么是迭代器之后,你理解生成器就比较容易了 [7:22] 码农高天: send 函数只是给你一个机制,让你可以去和你的 generator 做沟通和交流,可以改变他的一些状态。

Original transcript

[0:00] 这期视频我们讲生成器啊这个内容之前也是提到过很多次啊但是一直都没有系统的讲上期视频刚讲完迭代期我们顺便就把这个生成期讲一下那强烈推荐大家在看今天的生成器视频之前先把上一期迭代器的视频看完为什么呢因为生成器就是一种特殊的迭代器当你理解了什么是迭代器之后你理解生成器就比较容易了那生成器跟迭代器有很多相似的地方比如说左边这一块我们的G就是一个生成器由于生成器本身就是一个特殊的迭代器所以我们当然可以用forI in G这种方式来使用生成器我们看一下这个程序的结果很快打出了54321啊我们先别管这个生成技是怎么运行的生成器跟迭代器同样相似的地方就是它也可以用next弹数我们看我们可以通过next弹数把它第一个直线拿出来然后再做一个for loop这样打出来的就是432亿了生成器跟迭代器相似的性质和用法我们来这期视频就不讲了大家可以去看迭代器的部分我们这期主要讲他们不同的地方那在我们聊之前呢 首先要区分两个概念 这两个概念经常被毁用啊就是生成器函数和生成器对象我们有时候管这两个东西都叫生成器那相比于我们之前介绍的迭代器对吧它就是一个正儿八经的class然后class给它做一个实力化然后出来一个对象这都是非常基础的语法所以你不是那么容易混淆但是在生成期这块会稍有不同这个DEF站的站我们叫做生成器函数然后G等于占5G我们叫做生成器对象那如果你是一个对python半熟不熟的状态呢你看到这可能会觉得很奇怪对吧因为这个gen里面有一个return然后你就想着这个战务不应该直接return一个none留给g吗那这个地方是python在编译时期的一个小trick就是当他发现一个函数的定义里面有yield这个关键词的时候他就不会把这个函数当成一个普通的函数来处理Python会给这个函数打上一个标签 然后说这是一个生成器函数虽然它和我们平时使用的函数都一样都是def 然后一个函数名 然后括号但是这两个函数在python的眼里是完全不同的调用生成器函数会生成一个生成器对象有点像个绕口令就是当你做这个第七行这个gen5的时候它不会返回值而会返回一个生成器对象保存到G里一定要记住这一点啊就是这个生成器函数返回这个生成器对象里面这个yield return都不是它的返回值只有当你对生成器对象使用next函数的时候他才开始真正运行他的函数本体我们老规矩看一下它的bytecode生成器函数比较有意思的bytecode就是yieldvalue当next这个函数作用于这个生成器的时候他会运行这段code object然后运行到Ild Value的时候发生了什么事这个bytecode其实非常简单它就是把站顶的那个值拿出来存到red valley然后把当前的frame label一下说frame还没进行完并且存了一下战现在的深度然后就退出了go to exiting还记得吧我们之前讲过就是直接return value去了所以当你在做Next G的时候这个函数就在正常的运行然后到Yield Value的这个地方他把这个number给返回回去了但是这个函数没有运行完这个函数相当于被按了一个暂停键在下一次你再对这个生成器对象使用next时候他会接着这个eield value运行下面的代码那如果我们来看python这边代表的话 就更好理解一点 在你做g等于gen5的时候你把number已经复制好了但是你没有运行这段函数而是生成了一个生成机对象当你运行Next G的时候他先判断了5是不是大于0如果是的话他把5先返回然后这个函数暂停在这块所以这个时候first是五然后下面for i in g我们已经说过对不对就是在这个g里面不断的拿值每一次相当于都对他call了一个next下一次在call next的时候呢num就会减等于1那么就会变成4然后依然在这个Wave Loop里然后把这个4又出去在进行若干次之后NUM到0了这个时候他会运行这个return这里注意一下 由于它是在生成器函数里面所以return等价于raise stop iteration注意啊这个return不管你有没有return值它都不会在你调用next的时候被返回回去只有业务的值会被存起来那如果 [5:00] 你想拿到这个return value的话你需要去catch那个stop iteration的exception比如说我这里return100对吧我这个for loop是不会显示出来任何跟100有关的事的大家可以看到从使用者的角度看生成器和迭代器是没什么区别的他们的使用方法几乎是一样的从背后的原理看我们正常定义的迭代器也就是iterator它是把迭代时候的状态保存在一个对象里的而生成器也就是generator它实际上是把这个状态保存在了frame里就它的状态是函数运行到哪一步了而不是通过一个变量来记录我现在的状态那生成器这种形式有时候会让你的函数更为简洁我们还用昨天的链表来举一个例子我们之前是做了一个链表本身的Iterator对不对然后把这个node变成一个Iterable这一次我们也是想把这个node变成Iterable但是这个Iter函数我们直接把它做成一个生成器也就是当你做IterNode的时候它返回的是一个generator object但是我们说过generator object本身就是一个iterator对不对这个generator object就是从我本身开始然后eel了出去然后换我后面的那个直到这个node是none为止这种写法看上去就比我们上一期视频迭代器的那个写法要简洁非常多它就是用frame包括里面的local variable还有它运行到哪儿来表示状态而不是用单独的variable我们跑一下这段程序可以看到结果是跟我们之前的视频是一样的这里就体现出了生成器的一个优势在某些情况尤其是比较简单的情况下它的代码量会比传统的迭代器要少不少好 我们之前说生成器的用法和迭代器几乎是一样的为什么这加了一个几乎因为生成器本身还有一个比较高级的用法用法就是sendsend是什么意思呢send就是在这个生成器yield之后把这个yield什么东西变成一个值然后这个值还可以继续复给这个生成器函数里的其他变量我们看我把这个生成器函数稍微改了一下之前呢就是eo然后呢减等一完事了这次呢我把这个eo先复制给了temp然后检查temp是不是num如果temp不是num的话我们就把num重新复制这个语法可能看着有点怪啊这里是什么意思呢就是他首先会先eo就把这个值啊不管你是用next取的还是用send取的会把这个值返回出去然后接下来它会等你send的这个值拿进来你可以理解为这个evalu进行了一次evaluationevaluate成了send进来的这个值我们拿左边这段程序举个例子first等于nextg这个部分是一样的先evalu出来一个5所以first肯定是5然后这个时候我们g.send的10是什么意思呢就是这个evalu我们认为它是10我们把这个10给进去了然后就会把这个10复制给temp接下来num就会被复制成10然后num再简易在下一次eoNum的时候出来的就是9了你可能会问对不对下面这个forI in g呢我们说过forI in g相当于是不停的用next对不对那么对于生成器来说nextg和g.3的none是一模一样的我们看这里写了一个注释他们两个是完全等价的所以当你在做for i in g的时候你相当于在不停的做g.3的none然后这个temp就会一直被复制成none然后这个number就不会改变我们运行一下左边这段程序可以看到跟我们刚才讲的一样first是5对不对然后下面那个send是9因为num先被复制成了10然后又减了1所以下一次eo出来是9接下来这个for loop就从8打到了1我们注意一下这个生成器跟我们上一个写的生成器我都没有写return没写return和最后returnnum是一样的在python的任何一个函数里都是这样的当然了如果你的生成器里面本来eo num只是作为一个所谓statement存在并没有把它复值给任何东西的时候你send进去的数值就相当于被扔掉了它不会起作用但它也不会break你这个generator那如果这么简单的话呢我们就看第一个还是5对吧然后你send10就相当于把这个10给进去然后这个10被扔掉了然后那么还是正常减1然后这个值就是4对吧然后后面是321 [10:00] 你这个yield什么东西没有把它再复制给一个变量的时候你send任何东西包括sendnone跟用next一样的是等价的San的函数只是给你一个机制让你可以去和你的generator做沟通和交流可以改变他的一些状态好那这期有关生成器的视频就讲到这里希望大家有机会可以尝试一下如果你喜欢的话记得点一下关注别忘了一键三连哦字幕志愿者 杨茜茜