作者: andylin02
学习章节: 第 17 章 迭代器、生成器和经典协程
关键词: 迭代器|可迭代对象|生成器|yield|生成器表达式|协程|send|throw|close|itertools|惰性求值|yield from
一、本章概述
第 17 章是《流畅的 Python》中关于“控制流程”部分的开始,深入探讨了 Python 中迭代器、生成器和经典协程(基于生成器的协程)的核心概念。这些特性是 Python 实现惰性求值、高效处理大数据流、以及构建协作式多任务的基础。
“迭代器是 yield 的起源,而 yield 又是协程的基石。” —— David Beazley
本章主要内容结构:
- 可迭代对象与迭代器协议:理解 __iter__ 和 __next__ 方法,以及 for 循环背后的工作原理。
- 生成器函数:使用 yield 关键字简化迭代器的创建,实现惰性产生值的序列。
- 生成器表达式:简洁的惰性评估列表推导替代方案。
- 标准库中的生成器工具:itertools 模块提供了大量用于处理生成器的迭代器构建块,如 islice、cycle、chain、takewhile 等。
- 经典协程:生成器通过 .send()、.throw() 和 .close() 方法可以作为“协程”使用,实现数据的双向传递和协作式多任务。
- 从迭代器到协程的演进:理解生成器如何从简单的迭代器演变为拥有独立执行状态的协程。
前置知识:第 12 章(序列协议)和第 14 章(迭代器协议介绍)有助于更好地理解本章。
二、迭代器与可迭代对象
2.1 概念区分
在 Python 中,理解“可迭代对象”和“迭代器”的区别至关重要。
| 可迭代对象 | 实现了 __iter__ 方法的对象,该方法返回一个迭代器。任何可以用于 for 循环的对象都是可迭代对象。 | list, tuple, str, dict, file 等 | __iter__ |
| 迭代器 | 实现了 __next__ 方法的对象,且 __iter__ 返回自身。迭代器是惰性的,一次产出一个值,直到 StopIteration。 | 生成器对象、iter(list) 返回的对象 | __next__ 和 __iter__ |
关系:可迭代对象通过 iter() 获得迭代器;迭代器在 for 循环中不断调用 next() 获取值。
# 示例:迭代器协议
s = 'ABC' # s 是可迭代对象
it = iter(s) # 通过 iter() 获得迭代器
print(next(it)) # 'A'
print(next(it)) # 'B'
print(next(it)) # 'C'
# print(next(it)) # StopIteration 异常
2.2 实现可迭代对象
任何实现了 __iter__ 方法的类都是可迭代对象。通常 __iter__ 应该返回一个实现了 __next__ 的迭代器。
class Sentence:
def __init__(self, text):
self.text = text
self.words = text.split()
def __iter__(self):
# 返回一个迭代器(通常是生成器,但这里展示传统写法)
return SentenceIterator(self.words)
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
s = Sentence('Hello world Python')
for word in s:
print(word) # Hello world Python
更 Pythonic 的方式是使用生成器函数作为 __iter__ 方法(见下一节)。
2.3 迭代器协议流程图
#mermaid-svg-TJb1epWx70nQ6Y92{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TJb1epWx70nQ6Y92 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TJb1epWx70nQ6Y92 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TJb1epWx70nQ6Y92 .error-icon{fill:#552222;}#mermaid-svg-TJb1epWx70nQ6Y92 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TJb1epWx70nQ6Y92 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TJb1epWx70nQ6Y92 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TJb1epWx70nQ6Y92 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TJb1epWx70nQ6Y92 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TJb1epWx70nQ6Y92 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TJb1epWx70nQ6Y92 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TJb1epWx70nQ6Y92 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TJb1epWx70nQ6Y92 .marker.cross{stroke:#333333;}#mermaid-svg-TJb1epWx70nQ6Y92 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TJb1epWx70nQ6Y92 p{margin:0;}#mermaid-svg-TJb1epWx70nQ6Y92 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-TJb1epWx70nQ6Y92 .cluster-label text{fill:#333;}#mermaid-svg-TJb1epWx70nQ6Y92 .cluster-label span{color:#333;}#mermaid-svg-TJb1epWx70nQ6Y92 .cluster-label span p{background-color:transparent;}#mermaid-svg-TJb1epWx70nQ6Y92 .label text,#mermaid-svg-TJb1epWx70nQ6Y92 span{fill:#333;color:#333;}#mermaid-svg-TJb1epWx70nQ6Y92 .node rect,#mermaid-svg-TJb1epWx70nQ6Y92 .node circle,#mermaid-svg-TJb1epWx70nQ6Y92 .node ellipse,#mermaid-svg-TJb1epWx70nQ6Y92 .node polygon,#mermaid-svg-TJb1epWx70nQ6Y92 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TJb1epWx70nQ6Y92 .rough-node .label text,#mermaid-svg-TJb1epWx70nQ6Y92 .node .label text,#mermaid-svg-TJb1epWx70nQ6Y92 .image-shape .label,#mermaid-svg-TJb1epWx70nQ6Y92 .icon-shape .label{text-anchor:middle;}#mermaid-svg-TJb1epWx70nQ6Y92 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TJb1epWx70nQ6Y92 .rough-node .label,#mermaid-svg-TJb1epWx70nQ6Y92 .node .label,#mermaid-svg-TJb1epWx70nQ6Y92 .image-shape .label,#mermaid-svg-TJb1epWx70nQ6Y92 .icon-shape .label{text-align:center;}#mermaid-svg-TJb1epWx70nQ6Y92 .node.clickable{cursor:pointer;}#mermaid-svg-TJb1epWx70nQ6Y92 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TJb1epWx70nQ6Y92 .arrowheadPath{fill:#333333;}#mermaid-svg-TJb1epWx70nQ6Y92 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TJb1epWx70nQ6Y92 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TJb1epWx70nQ6Y92 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TJb1epWx70nQ6Y92 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TJb1epWx70nQ6Y92 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TJb1epWx70nQ6Y92 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TJb1epWx70nQ6Y92 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TJb1epWx70nQ6Y92 .cluster text{fill:#333;}#mermaid-svg-TJb1epWx70nQ6Y92 .cluster span{color:#333;}#mermaid-svg-TJb1epWx70nQ6Y92 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TJb1epWx70nQ6Y92 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TJb1epWx70nQ6Y92 rect.text{fill:none;stroke-width:0;}#mermaid-svg-TJb1epWx70nQ6Y92 .icon-shape,#mermaid-svg-TJb1epWx70nQ6Y92 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TJb1epWx70nQ6Y92 .icon-shape p,#mermaid-svg-TJb1epWx70nQ6Y92 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TJb1epWx70nQ6Y92 .icon-shape .label rect,#mermaid-svg-TJb1epWx70nQ6Y92 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TJb1epWx70nQ6Y92 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TJb1epWx70nQ6Y92 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TJb1epWx70nQ6Y92 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
iter()
for 循环使用
for 循环
调用 iter(obj) 获取迭代器
反复调用 next(iterator)
捕获 StopIteration 结束
迭代器
__next__ → 返回下一个值
__iter__ → 返回自身
可迭代对象
__iter__ → 返回迭代器
三、生成器函数
生成器函数是包含 yield 关键字的函数。调用生成器函数时,它返回一个生成器对象(该对象是迭代器的子类型),不会立即执行函数体。每次调用 next() 时,函数执行到下一个 yield 并暂停,返回 yield 的值。当函数返回(或执行完)时,抛出 StopIteration。
3.1 基本生成器
def gen_123():
print('start')
yield 1
print('continue')
yield 2
print('end')
yield 3
g = gen_123()
print(next(g)) # start \\n 1
print(next(g)) # continue \\n 2
print(next(g)) # end \\n 3
# print(next(g)) # StopIteration
3.2 生成器作为迭代器的高级用法
生成器自动实现迭代器协议,因此非常适用于实现可迭代对象。
class Sentence:
def __init__(self, text):
self.text = text
self.words = text.split()
def __iter__(self):
for word in self.words:
yield word # 生成器函数作为 __iter__
s = Sentence('Hello world')
for w in s:
print(w)
3.3 惰性生成器:处理大数据文件
def read_large_file(file_path):
with open(file_path) as f:
for line in f:
yield line.strip()
for line in read_large_file('bigfile.txt'):
print(line)
四、生成器表达式
生成器表达式语法与列表推导类似,但使用圆括号而不是方括号。它惰性求值,每次只产出一个元素,适合处理大数据流。
# 列表推导:立即生成全部元素
l = [x*2 for x in range(10)] # 列表
# 生成器表达式:返回生成器对象
g = (x*2 for x in range(10)) # 生成器
print(next(g)) # 0
print(next(g)) # 2
# 可以传递给函数,无需额外括号
sum_of_squares = sum(x*x for x in range(1000000)) # 高效,不产生完整列表
生成器表达式 vs 列表推导对比图
#mermaid-svg-8IEAUzML9Y0CzOMv{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8IEAUzML9Y0CzOMv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8IEAUzML9Y0CzOMv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8IEAUzML9Y0CzOMv .error-icon{fill:#552222;}#mermaid-svg-8IEAUzML9Y0CzOMv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8IEAUzML9Y0CzOMv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8IEAUzML9Y0CzOMv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8IEAUzML9Y0CzOMv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8IEAUzML9Y0CzOMv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8IEAUzML9Y0CzOMv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8IEAUzML9Y0CzOMv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8IEAUzML9Y0CzOMv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8IEAUzML9Y0CzOMv .marker.cross{stroke:#333333;}#mermaid-svg-8IEAUzML9Y0CzOMv svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8IEAUzML9Y0CzOMv p{margin:0;}#mermaid-svg-8IEAUzML9Y0CzOMv .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-8IEAUzML9Y0CzOMv .cluster-label text{fill:#333;}#mermaid-svg-8IEAUzML9Y0CzOMv .cluster-label span{color:#333;}#mermaid-svg-8IEAUzML9Y0CzOMv .cluster-label span p{background-color:transparent;}#mermaid-svg-8IEAUzML9Y0CzOMv .label text,#mermaid-svg-8IEAUzML9Y0CzOMv span{fill:#333;color:#333;}#mermaid-svg-8IEAUzML9Y0CzOMv .node rect,#mermaid-svg-8IEAUzML9Y0CzOMv .node circle,#mermaid-svg-8IEAUzML9Y0CzOMv .node ellipse,#mermaid-svg-8IEAUzML9Y0CzOMv .node polygon,#mermaid-svg-8IEAUzML9Y0CzOMv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8IEAUzML9Y0CzOMv .rough-node .label text,#mermaid-svg-8IEAUzML9Y0CzOMv .node .label text,#mermaid-svg-8IEAUzML9Y0CzOMv .image-shape .label,#mermaid-svg-8IEAUzML9Y0CzOMv .icon-shape .label{text-anchor:middle;}#mermaid-svg-8IEAUzML9Y0CzOMv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8IEAUzML9Y0CzOMv .rough-node .label,#mermaid-svg-8IEAUzML9Y0CzOMv .node .label,#mermaid-svg-8IEAUzML9Y0CzOMv .image-shape .label,#mermaid-svg-8IEAUzML9Y0CzOMv .icon-shape .label{text-align:center;}#mermaid-svg-8IEAUzML9Y0CzOMv .node.clickable{cursor:pointer;}#mermaid-svg-8IEAUzML9Y0CzOMv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8IEAUzML9Y0CzOMv .arrowheadPath{fill:#333333;}#mermaid-svg-8IEAUzML9Y0CzOMv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8IEAUzML9Y0CzOMv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8IEAUzML9Y0CzOMv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8IEAUzML9Y0CzOMv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8IEAUzML9Y0CzOMv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8IEAUzML9Y0CzOMv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8IEAUzML9Y0CzOMv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8IEAUzML9Y0CzOMv .cluster text{fill:#333;}#mermaid-svg-8IEAUzML9Y0CzOMv .cluster span{color:#333;}#mermaid-svg-8IEAUzML9Y0CzOMv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8IEAUzML9Y0CzOMv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8IEAUzML9Y0CzOMv rect.text{fill:none;stroke-width:0;}#mermaid-svg-8IEAUzML9Y0CzOMv .icon-shape,#mermaid-svg-8IEAUzML9Y0CzOMv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8IEAUzML9Y0CzOMv .icon-shape p,#mermaid-svg-8IEAUzML9Y0CzOMv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8IEAUzML9Y0CzOMv .icon-shape .label rect,#mermaid-svg-8IEAUzML9Y0CzOMv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8IEAUzML9Y0CzOMv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8IEAUzML9Y0CzOMv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8IEAUzML9Y0CzOMv :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
生成器表达式
返回生成器对象
惰性求值,逐个产出
只能迭代一次
列表推导
构建整个列表
占用内存
可重复使用
五、标准库中的生成器工具:itertools
itertools 模块提供了许多用于操作迭代器的生成器函数。以下是几种常用类型:
5.1 无限迭代器
| count(start, step) | 从 start 开始无限递增 | count(10, 2) → 10, 12, 14, … |
| cycle(iterable) | 无限重复迭代序列 | cycle('AB') → A, B, A, B, … |
| repeat(elem, n) | 重复元素 n 次(默认无限) | repeat(7, 3) → 7, 7, 7 |
5.2 有限迭代器(组合、选择)
| chain(*iters) | 将多个可迭代对象串联 | chain('AB', [1,2]) → A, B, 1, 2 |
| zip_longest(*iters, fillvalue) | 类似 zip,但用 fillvalue 填充短边 | zip_longest('AB', '123', fillvalue='?') → (A,1), (B,2), (?,3) |
| islice(iter, start, stop, step) | 切片迭代器 | islice(count(0), 5) → 0,1,2,3,4 |
| takewhile(predicate, iter) | 满足条件时产出,一旦不满足停止 | takewhile(lambda x: x<5, count(0)) → 0,1,2,3,4 |
5.3 组合生成器
| product(*iters, repeat=1) | 笛卡尔积 | product('AB', repeat=2) → AA, AB, BA, BB |
| permutations(iter, r) | 长度为 r 的排列 | permutations('ABC', 2) → AB, AC, BA, BC, CA, CB |
| combinations(iter, r) | 按顺序的组合 | combinations('ABC', 2) → AB, AC, BC |
import itertools
# 生成前 10 个偶数
evens = list(itertools.islice(itertools.count(0, 2), 10))
print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# 笛卡尔积
colors = ['red', 'green']
sizes = ['S', 'M']
product = list(itertools.product(colors, sizes))
print(product) # [('red', 'S'), ('red', 'M'), ('green', 'S'), ('green', 'M')]
六、经典协程:生成器的进阶
Python 2.5 为生成器增加了 .send(value)、.throw(type, value, tb) 和 .close() 方法,使得生成器不仅可以产出值,还可以接收外部传入的值,从而成为“协程”。
协程与生成器的区别:
- 生成器:产生值给调用者。调用者使用 next() 或 for 循环获取值。
- 协程:消耗(接收)值,同时也可以产出值。调用者使用 .send(value) 将值发送给协程,协程用 yield 接收。
6.1 协程的状态
协程有四种状态,可以通过 inspect.getgeneratorstate(gen) 查看:
- GEN_CREATED:等待开始执行
- GEN_RUNNING:正在执行(通常只在多线程中看到)
- GEN_SUSPENDED:在 yield 表达式处暂停
- GEN_CLOSED:执行结束或已关闭
6.2 一个简单的协程:累加器
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield average # 接收值,产出平均值
total += term
count += 1
average = total / count
coro = averager()
next(coro) # 预激协程,执行到 yield,产出 None
print(coro.send(10)) # 10.0
print(coro.send(20)) # 15.0
print(coro.send(30)) # 20.0
coro.close() # 关闭协程
重要:协程必须通过 next(coro) 或 coro.send(None) 预激,使其前进到第一个 yield 表达式。否则无法发送值。
6.3 使用装饰器预激协程
from functools import wraps
def coroutine(func):
@wraps(func)
def primer(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen) # 预激
return gen
return primer
@coroutine
def averager():
total = 0.0
count = 0
avg = None
while True:
term = yield avg
total += term
count += 1
avg = total / count
coro = averager() # 自动预激
print(coro.send(10)) # 10.0
6.4 协程的异常处理与关闭
- generator.throw(exc_type[, exc_value[, traceback]]):在协程的当前 yield 处抛出异常。如果协程捕获并处理了异常,可以继续运行;否则协程终止。
- generator.close():在协程的当前 yield 处抛出 GeneratorExit 异常。协程应捕获该异常以执行清理(通常不需要特殊处理),若不处理,协程直接终止。
@coroutine
def averager():
total = 0
count = 0
avg = None
try:
while True:
term = yield avg
total += term
count += 1
avg = total / count
except GeneratorExit:
print('协程关闭')
except ValueError:
print('收到无效值,跳过')
coro = averager()
coro.send(10)
coro.throw(ValueError, 'bad data') # 输出"收到无效值,跳过",协程继续
coro.send(20) # 正常
coro.close() # 输出"协程关闭"
七、从生成器到协程的演进图
#mermaid-svg-gflRcCRH9fIsFQeo{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gflRcCRH9fIsFQeo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gflRcCRH9fIsFQeo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gflRcCRH9fIsFQeo .error-icon{fill:#552222;}#mermaid-svg-gflRcCRH9fIsFQeo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gflRcCRH9fIsFQeo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gflRcCRH9fIsFQeo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gflRcCRH9fIsFQeo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gflRcCRH9fIsFQeo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gflRcCRH9fIsFQeo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gflRcCRH9fIsFQeo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gflRcCRH9fIsFQeo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gflRcCRH9fIsFQeo .marker.cross{stroke:#333333;}#mermaid-svg-gflRcCRH9fIsFQeo svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gflRcCRH9fIsFQeo p{margin:0;}#mermaid-svg-gflRcCRH9fIsFQeo defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-gflRcCRH9fIsFQeo g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-gflRcCRH9fIsFQeo g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-gflRcCRH9fIsFQeo g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-gflRcCRH9fIsFQeo g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-gflRcCRH9fIsFQeo g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-gflRcCRH9fIsFQeo .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-gflRcCRH9fIsFQeo .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-gflRcCRH9fIsFQeo .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-gflRcCRH9fIsFQeo .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-gflRcCRH9fIsFQeo .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-gflRcCRH9fIsFQeo .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-gflRcCRH9fIsFQeo .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-gflRcCRH9fIsFQeo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gflRcCRH9fIsFQeo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gflRcCRH9fIsFQeo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gflRcCRH9fIsFQeo .edgeLabel .label text{fill:#333;}#mermaid-svg-gflRcCRH9fIsFQeo .label div .edgeLabel{color:#333;}#mermaid-svg-gflRcCRH9fIsFQeo .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-gflRcCRH9fIsFQeo .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-gflRcCRH9fIsFQeo .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-gflRcCRH9fIsFQeo .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-gflRcCRH9fIsFQeo .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-gflRcCRH9fIsFQeo .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gflRcCRH9fIsFQeo .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gflRcCRH9fIsFQeo #statediagram-barbEnd{fill:#333333;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gflRcCRH9fIsFQeo .cluster-label,#mermaid-svg-gflRcCRH9fIsFQeo .nodeLabel{color:#131300;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-gflRcCRH9fIsFQeo .note-edge{stroke-dasharray:5;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-note text{fill:black;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram-note .nodeLabel{color:black;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagram .edgeLabel{color:red;}#mermaid-svg-gflRcCRH9fIsFQeo #dependencyStart,#mermaid-svg-gflRcCRH9fIsFQeo #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-gflRcCRH9fIsFQeo .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gflRcCRH9fIsFQeo :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
调用生成器函数
next(g)
next(g) / send(v)
执行完毕 / throw / close
GEN_CREATED
GEN_SUSPENDED
GEN_CLOSED
八、综合案例:带协程的实时平均数计算
def running_average():
total = 0
count = 0
avg = 0
while True:
value = yield avg
if value is None: # 允许用 send(None) 退出循环
break
total += value
count += 1
avg = total / count
return (total, count, avg) # 协程的返回值会附加到 StopIteration 异常上
def use_avg():
coro = running_average()
next(coro)
print(coro.send(10))
print(coro.send(20))
print(coro.send(30))
try:
coro.send(None)
except StopIteration as e:
result = e.value
print(f"Total: {result[0]}, Count: {result[1]}, Average: {result[2]}")
use_avg()
九、经典协程与 yield from
本章节仅简要提及 yield from,因为《流畅的 Python》第 2 版中,经典协程(基于生成器的协程)和原生协程(async/await)分属不同章节。yield from 将在下一章详细说明。
yield from 可以将迭代任务委托给另一个生成器,简化协程中嵌套子协程的代码,同时支持双向数据传递和异常传播。
十、本章思维导图
#mermaid-svg-7ja9jk6AkJnNzpk3{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7ja9jk6AkJnNzpk3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7ja9jk6AkJnNzpk3 .error-icon{fill:#552222;}#mermaid-svg-7ja9jk6AkJnNzpk3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7ja9jk6AkJnNzpk3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7ja9jk6AkJnNzpk3 .marker.cross{stroke:#333333;}#mermaid-svg-7ja9jk6AkJnNzpk3 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7ja9jk6AkJnNzpk3 p{margin:0;}#mermaid-svg-7ja9jk6AkJnNzpk3 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-7ja9jk6AkJnNzpk3 .cluster-label text{fill:#333;}#mermaid-svg-7ja9jk6AkJnNzpk3 .cluster-label span{color:#333;}#mermaid-svg-7ja9jk6AkJnNzpk3 .cluster-label span p{background-color:transparent;}#mermaid-svg-7ja9jk6AkJnNzpk3 .label text,#mermaid-svg-7ja9jk6AkJnNzpk3 span{fill:#333;color:#333;}#mermaid-svg-7ja9jk6AkJnNzpk3 .node rect,#mermaid-svg-7ja9jk6AkJnNzpk3 .node circle,#mermaid-svg-7ja9jk6AkJnNzpk3 .node ellipse,#mermaid-svg-7ja9jk6AkJnNzpk3 .node polygon,#mermaid-svg-7ja9jk6AkJnNzpk3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7ja9jk6AkJnNzpk3 .rough-node .label text,#mermaid-svg-7ja9jk6AkJnNzpk3 .node .label text,#mermaid-svg-7ja9jk6AkJnNzpk3 .image-shape .label,#mermaid-svg-7ja9jk6AkJnNzpk3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-7ja9jk6AkJnNzpk3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7ja9jk6AkJnNzpk3 .rough-node .label,#mermaid-svg-7ja9jk6AkJnNzpk3 .node .label,#mermaid-svg-7ja9jk6AkJnNzpk3 .image-shape .label,#mermaid-svg-7ja9jk6AkJnNzpk3 .icon-shape .label{text-align:center;}#mermaid-svg-7ja9jk6AkJnNzpk3 .node.clickable{cursor:pointer;}#mermaid-svg-7ja9jk6AkJnNzpk3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7ja9jk6AkJnNzpk3 .arrowheadPath{fill:#333333;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7ja9jk6AkJnNzpk3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7ja9jk6AkJnNzpk3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7ja9jk6AkJnNzpk3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7ja9jk6AkJnNzpk3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7ja9jk6AkJnNzpk3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7ja9jk6AkJnNzpk3 .cluster text{fill:#333;}#mermaid-svg-7ja9jk6AkJnNzpk3 .cluster span{color:#333;}#mermaid-svg-7ja9jk6AkJnNzpk3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7ja9jk6AkJnNzpk3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7ja9jk6AkJnNzpk3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-7ja9jk6AkJnNzpk3 .icon-shape,#mermaid-svg-7ja9jk6AkJnNzpk3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7ja9jk6AkJnNzpk3 .icon-shape p,#mermaid-svg-7ja9jk6AkJnNzpk3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7ja9jk6AkJnNzpk3 .icon-shape .label rect,#mermaid-svg-7ja9jk6AkJnNzpk3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7ja9jk6AkJnNzpk3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7ja9jk6AkJnNzpk3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7ja9jk6AkJnNzpk3 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
第17章 迭代器、生成器和经典协程
可迭代对象 vs 迭代器
__iter__ 返回迭代器
__next__ + __iter__ 返回自身
生成器函数
yield 关键字
惰性求值
函数状态暂停/恢复
生成器表达式
圆括号语法
逐个产出,节省内存
itertools 模块
count, cycle, repeat
product, permutations, combinations
islice, takewhile, chain
经典协程
.send(value) 发送数据
.throw(exc) 注入异常
.close() 关闭协程
预激: next(c) 或 send(None)
GEN_CREATED, SUSPENDED, CLOSED
yield from 简述
委托给子生成器
十一、常见错误与最佳实践
| 迭代时修改被迭代的集合 | 迭代器持有的索引失效 | 迭代前复制一份,如 list(items) 或使用切片 |
| 忘记预激协程 | 协程未执行到第一个 yield,send 会失败 | 使用装饰器自动预激,或显式调用 next(coro) |
| 在生成器中抛出 StopIteration | 早期 Python 版本允许,3.5+ 会引发 RuntimeError | 使用 return 返回值(附加到异常对象) |
| 对迭代器重复迭代 | 迭代器只能使用一次 | 需要重新迭代时,从可迭代对象重新获取迭代器 |
| 生成器表达式需要加括号 | 作为唯一参数时可省略,否则必须加括号 | 明确 sum(x for x in …) 不需要,但 f(1, x for x in …) 错误 |
最佳实践:
- 优先使用生成器函数和生成器表达式代替手写迭代器类。
- 处理大型数据流时,始终使用生成器/迭代器,避免构建完整的中间列表。
- 对于协程,使用 @coroutine 装饰器自动预激,避免遗忘导致奇怪错误。
- 使用 itertools 模块的丰富函数,避免自己实现复杂的迭代逻辑。
- 理解协程的四种状态,调试时通过 inspect.getgeneratorstate() 检查。
十二、本章总结
第 17 章是 Python 异步编程和控制流程的重要基石,系统讲解了:
- 迭代器与可迭代对象:通过实现 __iter__ 和 __next__ 可以创建自己的序列类型。
- 生成器函数与表达式:使用 yield 替代 __next__ 和 __iter__ 的样板代码,是 Python 中最简洁优雅的迭代器实现方式。
- itertools 模块:提供了标准的高阶迭代器工具,能大幅简化数据处理代码。
- 经典协程:生成器扩展了 .send() 方法后变为协程,允许双向通信。协程可以维护状态,适合实现数据流处理管道。
- 协程的生命周期:预激 → 发送值 → 可能异常 → 关闭。
掌握了本章内容,你就能够编写高效、惰性、可组合的数据处理代码,并为学习基于 async/await 的原生协程打下坚实基础。
十三、思考题
十四、下一章预告
第 18 章《使用 yield from》
在第 17 章学习了生成器作为协程的基础后,第 18 章将深入 yield from 这个关键语法。yield from 不仅简化了在生成器中委托给子生成器的代码,还能双向传递数据、处理异常、获取子生成器的返回值。它是将多个协程组合成管道或复杂流程的核心构造。
你将学到:
- yield from 的基本语法与行为
- 如何用 yield from 重构迭代器代码
- 用 yield from 编写协程委派器(delegating generator)
- 子生成器的返回值获取
- yield from 与异常传播
- 应用案例:使用 yield from 实现一个简单的任务调度器
第 18 章将为你打开高效构建协作式多任务系统的大门,并为后续学习 asyncio 打下坚实基础。
本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!





