【Python】第 7 章:生成器与协程

第 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 + yieldasync def + await
驱动next() / send()事件循环
用途迭代、管道异步 I/O、并发
返回值yieldreturn + await

Q2: 为什么协程需要事件循环?

A:

  • 协程不会自动执行
  • 事件循环调度和驱动协程
  • 管理 I/O 多路复用
  • 处理并发和任务调度

Q3: yieldyield 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 与并发,包括全局解释器锁、多线程和多进程的对比。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值