第 7 章:生成器与协程
7.1 迭代器协议
原理讲解
迭代器协议(Iterator Protocol):
┌─────────────────────────────────────────────────────────┐
│ Python 迭代器协议 │
├─────────────────────────────────────────────────────────┤
│ │
│ 可迭代对象 (Iterable): │
│ - 实现 __iter__() 方法 │
│ - 返回迭代器对象 │
│ - 例如:list, tuple, dict, str, set │
│ │
│ 迭代器 (Iterator): │
│ - 实现 __iter__() 方法 (返回自身) │
│ - 实现 __next__() 方法 (返回下一个值) │
│ - 耗尽时抛出 StopIteration 异常 │
│ │
│ 协议流程: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ for item in iterable: │ │
│ │ process(item) │ │
│ │ │ │
│ │ 内部实现: │ │
│ │ iterator = iterable.__iter__() │ │
│ │ while True: │ │
│ │ try: │ │
│ │ item = iterator.__next__() │ │
│ │ process(item) │ │
│ │ except StopIteration: │ │
│ │ break │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
手动实现迭代器:
class CountDown:
"""倒计时迭代器"""
def __init__(self, start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start <= 0:
raise StopIteration
self.start -= 1
return self.start + 1
# 使用
for i in CountDown(5):
print(i) # 5, 4, 3, 2, 1
生成器函数
生成器 = 自动实现迭代器协议
# 普通函数
def countdown_normal(n):
result = []
for i in range(n, 0, -1):
result.append(i)
return result # 一次性返回所有值
# 生成器函数
def countdown_generator(n):
for i in range(n, 0, -1):
yield i # 每次返回一个值,暂停
生成器特点:
┌─────────────────────────────────────────────────────────┐
│ 生成器 vs 普通函数 │
├─────────────────────────────────────────────────────────┤
│ │
│ 普通函数: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ def func(): │ │
│ │ return 1 │ │
│ │ return 2 # 永远不会执行 │ │
│ │ │ │
│ │ 调用:返回结果,函数结束 │ │
│ │ 内存:所有结果在内存中 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 生成器函数: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ def gen(): │ │
│ │ yield 1 # 暂停,保存状态 │ │
│ │ yield 2 # 恢复,继续执行 │ │
│ │ │ │
│ │ 调用:返回生成器对象,不执行代码 │ │
│ │ 内存:惰性计算,节省内存 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
yield 的工作原理
生成器状态机:
┌─────────────────────────────────────────────────────────┐
│ yield 状态转换 │
├─────────────────────────────────────────────────────────┤
│ │
│ 生成器状态: │
│ ┌─────────────┐ │
│ │ CREATED │ 刚创建,未执行 │
│ └──────┬──────┘ │
│ │ next() │
│ ↓ │
│ ┌─────────────┐ │
│ │ RUNNING │ 正在执行 │
│ └──────┬──────┘ │
│ │ yield │
│ ↓ │
│ ┌─────────────┐ │
│ │ SUSPENDED │ 暂停,保存局部变量 │
│ └──────┬──────┘ │
│ │ next() │
│ ↓ │
│ ┌─────────────┐ │
│ │ RUNNING │ 从 yield 处恢复 │
│ └──────┬──────┘ │
│ │ return / 结束 │
│ ↓ │
│ ┌─────────────┐ │
│ │ CLOSED │ 抛出 StopIteration │
│ └─────────────┘ │
│ │
│ 生成器对象属性: │
│ - gi_frame: 栈帧对象 │
│ - gi_running: 是否正在运行 │
│ - gi_code: 代码对象 │
│ - gi_yieldfrom: yield from 的目标 │
│ │
└─────────────────────────────────────────────────────────┘
查看生成器状态:
def gen():
x = 1
yield x
x = 2
yield x
g = gen()
print(f"创建后:gi_running={g.gi_running}") # False
next(g)
print(f"第一次 yield 后:gi_running={g.gi_running}") # False
next(g)
print(f"第二次 yield 后:gi_running={g.gi_running}") # False
try:
next(g)
except StopIteration:
print("生成器已结束")
7.2 协程基础
async/await 原理
协程(Coroutine):可暂停的函数
┌─────────────────────────────────────────────────────────┐
│ async/await 原理 │
├─────────────────────────────────────────────────────────┤
│ │
│ async 函数: │
│ - 定义协程函数 │
│ - 调用返回协程对象(不执行) │
│ - 需要 await 或事件循环驱动 │
│ │
│ await 表达式: │
│ - 暂停当前协程 │
│ - 等待可等待对象(awaitable)完成 │
│ - 恢复时获取结果 │
│ │
│ 可等待对象: │
│ - 协程对象 │
│ - Task 对象 │
│ - 实现 __await__() 的对象 │
│ │
│ 执行流程: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ async def fetch(): │ │
│ │ result = await http.get() # 暂停 │ │
│ │ return result │ │
│ │ │ │
│ │ # 驱动协程 │ │
│ │ asyncio.run(fetch()) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
协程状态机:
import asyncio
import inspect
async def coro():
await asyncio.sleep(0.1)
return "done"
# 创建协程对象
coro_obj = coro()
# 检查类型
print(f"是协程:{inspect.iscoroutine(coro_obj)}") # True
# 查看状态(Python 3.8+)
# print(f"状态:{coro_obj.cr_running}")
# 运行协程
result = asyncio.run(coro_obj)
print(f"结果:{result}")
事件循环
事件循环(Event Loop)工作原理:
┌─────────────────────────────────────────────────────────┐
│ 事件循环架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 事件循环 (Event Loop) │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ while True: │ │ │
│ │ │ # 1. 运行就绪的回调 │ │ │
│ │ │ run_ready_callbacks() │ │ │
│ │ │ │ │ │
│ │ │ # 2. 处理 I/O 事件 │ │ │
│ │ │ process_io_events() │ │ │
│ │ │ │ │ │
│ │ │ # 3. 调度定时任务 │ │ │
│ │ │ schedule_timers() │ │ │
│ │ │ │ │ │
│ │ │ # 4. 如果没有任务,等待 │ │ │
│ │ │ if no_tasks: │ │ │
│ │ │ wait_for_events() │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ ↕ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 任务队列 │ │
│ │ [Task1, Task2, Task3, ...] │ │
│ │ │ │
│ │ 每个 Task 包装一个协程 │ │
│ └─────────────────────────────────────────────────┘ │
│ ↕ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ I/O 多路复用 │ │
│ │ - select (跨平台) │ │
│ │ - epoll (Linux) │ │
│ │ - kqueue (BSD/macOS) │ │
│ │ - IOCP (Windows) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
asyncio 基础
核心组件:
import asyncio
# 1. 创建事件循环
loop = asyncio.get_event_loop()
# 2. 运行协程
asyncio.run(main()) # Python 3.7+
# 3. 创建任务
task = asyncio.create_task(coro())
# 4. 等待多个任务
results = await asyncio.gather(task1, task2, task3)
# 5. 超时控制
try:
result = await asyncio.wait_for(coro(), timeout=5.0)
except asyncio.TimeoutError:
print("超时")
# 6. 并发限制
semaphore = asyncio.Semaphore(10)
async with semaphore:
await do_something()
7.3 实践:实现一个简单的协程
实验代码
# examples/chapter-07/generator_coroutine_examples.py
import asyncio
import time
from typing import Generator
print("=" * 70)
print("生成器与协程实践示例")
print("=" * 70)
# ========== 1. 生成器基础 ==========
print("\n【示例 1】生成器基础")
print("-" * 50)
def simple_generator():
"""简单生成器"""
yield 1
yield 2
yield 3
gen = simple_generator()
print(f"第一次 next: {next(gen)}")
print(f"第二次 next: {next(gen)}")
print(f"第三次 next: {next(gen)}")
# 使用 for 循环
print("\n使用 for 循环:")
for value in simple_generator():
print(f" 值:{value}")
# ========== 2. 生成器发送数据 ==========
print("\n【示例 2】生成器发送数据 (send)")
print("-" * 50)
def accumulator():
"""累加器生成器"""
total = 0
while True:
value = yield total
if value is None:
break
total += value
acc = accumulator()
print(f"初始值:{next(acc)}") # 启动生成器
print(f"发送 5: {acc.send(5)}")
print(f"发送 3: {acc.send(3)}")
print(f"发送 10: {acc.send(10)}")
acc.send(None) # 关闭
# ========== 3. yield from ==========
print("\n【示例 3】yield from 委托生成")
print("-" * 50)
def sub_generator():
yield "sub-1"
yield "sub-2"
return "sub-return"
def main_generator():
yield "main-1"
result = yield from sub_generator()
yield f"sub returned: {result}"
yield "main-2"
for value in main_generator():
print(f" {value}")
# ========== 4. 生成器实现管道 ==========
print("\n【示例 4】生成器管道")
print("-" * 50)
def numbers():
"""生成数字"""
for i in range(10):
yield i
def squares(seq):
"""计算平方"""
for n in seq:
yield n ** 2
def evens(seq):
"""过滤偶数"""
for n in seq:
if n % 2 == 0:
yield n
# 管道组合
pipeline = evens(squares(numbers()))
print(f"偶数平方:{list(pipeline)}")
# ========== 5. 异步协程基础 ==========
print("\n【示例 5】异步协程基础")
print("-" * 50)
async def async_hello():
"""异步 hello"""
print(" 协程开始")
await asyncio.sleep(0.1)
print(" 协程结束")
return "hello"
# 运行协程
result = asyncio.run(async_hello())
print(f"结果:{result}")
# ========== 6. 并发执行多个协程 ==========
print("\n【示例 6】并发执行多个协程")
print("-" * 50)
async def fetch_data(name, delay):
"""模拟获取数据"""
print(f" {name} 开始 (延迟 {delay}s)")
await asyncio.sleep(delay)
print(f" {name} 完成")
return f"{name} 的数据"
async def concurrent_fetch():
tasks = [
fetch_data("A", 0.3),
fetch_data("B", 0.2),
fetch_data("C", 0.1),
]
# 并发执行
start = time.time()
results = await asyncio.gather(*tasks)
elapsed = time.time() - start
print(f"\n总耗时:{elapsed:.2f}s (如果串行需要 0.6s)")
print(f"结果:{results}")
asyncio.run(concurrent_fetch())
# ========== 7. 异步上下文管理器 ==========
print("\n【示例 7】异步上下文管理器")
print("-" * 50)
class AsyncResource:
"""异步资源示例"""
async def __aenter__(self):
print(" 获取资源...")
await asyncio.sleep(0.1)
print(" 资源已获取")
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print(" 释放资源...")
await asyncio.sleep(0.1)
print(" 资源已释放")
async def do_work(self):
print(" 工作中...")
await asyncio.sleep(0.1)
async def use_resource():
async with AsyncResource() as res:
await res.do_work()
asyncio.run(use_resource())
# ========== 8. 生成器 vs 协程性能对比 ==========
print("\n【示例 8】生成器 vs 列表性能")
print("-" * 50)
def gen_range(n):
for i in range(n):
yield i
def list_range(n):
return list(range(n))
n = 1000000
# 生成器内存
import sys
gen_obj = gen_range(n)
print(f"生成器内存:{sys.getsizeof(gen_obj)} bytes")
# 列表内存
list_obj = list_range(1000) # 小一点的列表
print(f"列表内存 (1000 个): {sys.getsizeof(list_obj)} bytes")
print("\n生成器优势:惰性计算,节省内存")
print("列表优势:可重复迭代,支持索引")
实验练习
练习 1:实现范围生成器
def my_range(start, stop=None, step=1):
"""手动实现 range 生成器"""
if stop is None:
stop = start
start = 0
current = start
while (step > 0 and current < stop) or (step < 0 and current > stop):
yield current
current += step
# 测试
print("my_range(5):", list(my_range(5)))
print("my_range(2, 8):", list(my_range(2, 8)))
print("my_range(0, 10, 2):", list(my_range(0, 10, 2)))
print("my_range(10, 0, -1):", list(my_range(10, 0, -1)))
练习 2:实现异步生产者 - 消费者
import asyncio
import random
async def producer(queue, name, count):
"""生产者"""
for i in range(count):
item = f"{name}-{i}"
await queue.put(item)
print(f"生产:{item}")
await asyncio.sleep(random.uniform(0.01, 0.05))
await queue.put(None) # 结束标记
async def consumer(queue, name):
"""消费者"""
while True:
item = await queue.get()
if item is None:
break
print(f" {name} 消费:{item}")
await asyncio.sleep(random.uniform(0.02, 0.08))
queue.task_done()
async def main():
queue = asyncio.Queue(maxsize=5)
# 创建生产者和消费者
producers = [
producer(queue, "P1", 10),
producer(queue, "P2", 10),
]
consumers = [
consumer(queue, "C1"),
consumer(queue, "C2"),
]
# 并发运行
await asyncio.gather(*producers, *consumers)
asyncio.run(main())
练习 3:使用生成器处理大文件
def read_lines(filename):
"""惰性读取文件行"""
with open(filename, 'r') as f:
for line in f:
yield line.strip()
def filter_non_empty(lines):
"""过滤空行"""
for line in lines:
if line:
yield line
def transform_upper(lines):
"""转换为大写"""
for line in lines:
yield line.upper()
# 创建处理管道
def process_file(filename):
lines = read_lines(filename)
lines = filter_non_empty(lines)
lines = transform_upper(lines)
for line in lines:
print(line)
# 创建测试文件
with open('/tmp/test.txt', 'w') as f:
f.write("hello\n\nworld\n\npython\n")
# 处理
print("处理文件:")
process_file('/tmp/test.txt')
常见问题
Q1: 生成器和协程有什么区别?
A:
| 特性 | 生成器 | 协程 |
|---|---|---|
| 定义 | def + yield | async def + await |
| 驱动 | next() / send() | 事件循环 |
| 用途 | 迭代、管道 | 异步 I/O、并发 |
| 返回值 | yield | return + await |
Q2: 为什么协程需要事件循环?
A:
- 协程不会自动执行
- 事件循环调度和驱动协程
- 管理 I/O 多路复用
- 处理并发和任务调度
Q3: yield 和 yield from 的区别?
A:
# yield: 返回单个值
yield value
# yield from: 委托给另一个迭代器
yield from iterable # 等价于 for item in iterable: yield item
Q4: 如何关闭生成器?
A:
gen = generator()
next(gen)
# 方法 1: 发送 None
gen.send(None)
# 方法 2: 调用 close()
gen.close()
# 方法 3: 抛出异常
gen.throw(StopIteration)
Q5: async/await 和 threading 的区别?
A:
- asyncio: 单线程,协作式多任务,适合 I/O 密集
- threading: 多线程,抢占式多任务,适合 CPU 密集(但有 GIL 限制)
- multiprocessing: 多进程,真正并行,适合 CPU 密集
本章小结
- 迭代器协议:
__iter__()+__next__() - 生成器自动实现迭代器协议,使用
yield - 生成器惰性计算,节省内存
yield from委托给子生成器- 协程使用
async/await语法 - 事件循环驱动协程执行
- asyncio 提供异步 I/O 基础设施
下一章预告
第 8 章将深入探讨 GIL 与并发,包括全局解释器锁、多线程和多进程的对比。

269

被折叠的 条评论
为什么被折叠?



