🔄 高级魔法师修炼:生成器、迭代器和协程
欢迎来到Python异步编程的奇妙世界!在这一章节中,我们将深入探讨Python中处理数据流和异步操作的强大工具:生成器(Generators)、迭代器(Iterators) 和 协程(Coroutines)。这些特性不仅可以让你的代码更高效、更优雅,还能帮助你处理大规模数据和构建高性能的异步应用。
🎯 学习目标
通过本章的学习,你将能够:
- 深入理解迭代器和可迭代对象的概念与实现
- 熟练使用生成器函数和生成器表达式
- 掌握高级生成器特性和用法
- 理解协程的基本原理和Python异步编程模型
- 在实际项目中应用这些特性提高代码性能和可读性
🔍 一、迭代器的概念与实现
1.1 什么是迭代器和可迭代对象?
在Python中,我们经常使用for循环来遍历各种数据结构,如列表、元组、字典等。这背后其实隐藏着一个强大的机制:迭代器协议。
- 可迭代对象(Iterable):任何实现了
__iter__()方法的对象都是可迭代的。__iter__()方法返回一个迭代器。 - 迭代器(Iterator):任何实现了
__iter__()和__next__()方法的对象都是迭代器。__next__()方法返回下一个元素,如果没有更多元素,则抛出StopIteration异常。
举个例子,我们可以手动实现一个迭代器来理解这个概念:
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
# 使用我们的迭代器
countdown = Countdown(5)
for i in countdown:
print(i) # 输出: 5, 4, 3, 2, 1
# 尝试再次遍历(迭代器只能使用一次)
for i in countdown:
print(i) # 什么都不输出,因为迭代器已经用完了1.2 iter()和next()内置函数
Python提供了两个内置函数来操作迭代器:
iter(iterable):将可迭代对象转换为迭代器next(iterator, default=None):获取迭代器的下一个元素
让我们看看如何使用这两个函数:
# 使用iter()和next()
numbers = [1, 2, 3, 4, 5]
number_iterator = iter(numbers) # 获取迭代器
print(next(number_iterator)) # 输出: 1
print(next(number_iterator)) # 输出: 2
print(next(number_iterator)) # 输出: 3
print(next(number_iterator)) # 输出: 4
print(next(number_iterator)) # 输出: 5
print(next(number_iterator, "没有更多元素了")) # 输出: "没有更多元素了"
# 尝试再次获取下一个元素(会抛出异常)
# print(next(number_iterator)) # 将抛出 StopIteration 异常1.3 迭代器的优点
迭代器是Python的核心概念之一,它具有以下优点:
- 惰性计算:迭代器只在需要时才生成下一个元素,这对于处理大量数据或无限序列特别有用
- 内存效率:迭代器一次只存储一个元素,而不是整个序列
- 可组合性:可以轻松组合多个迭代器来构建复杂的数据处理管道
- 统一接口:Python的各种数据结构都实现了迭代器协议,提供了一致的遍历方式
🚀 二、生成器函数
2.1 什么是生成器?
生成器是一种特殊的迭代器,它使我们能够更简单地创建迭代器。生成器函数使用yield语句而不是return语句来返回值。
当调用生成器函数时,它不会执行函数体,而是返回一个生成器对象。每次调用生成器的__next__()方法时,函数体会执行到遇到的第一个yield语句,然后将yield的值返回,并暂停执行。下次调用__next__()时,函数从上次暂停的地方继续执行,直到遇到下一个yield语句或函数结束。
让我们看一个简单的生成器函数示例:
def countdown_generator(start):
"""一个倒计时生成器函数"""
while start > 0:
yield start
start -= 1
# 使用生成器函数
countdown = countdown_generator(5)
for i in countdown:
print(i) # 输出: 5, 4, 3, 2, 1
# 生成器也是迭代器,所以可以使用next()函数
countdown2 = countdown_generator(3)
print(next(countdown2)) # 输出: 3
print(next(countdown2)) # 输出: 2
print(next(countdown2)) # 输出: 1
# print(next(countdown2)) # 将抛出 StopIteration 异常2.2 生成器表达式
除了生成器函数外,Python还提供了一种更简洁的方式来创建生成器:生成器表达式。生成器表达式的语法类似于列表推导式,但使用圆括号而不是方括号。
# 生成器表达式示例
square_generator = (x*x for x in range(10))
# 使用生成器表达式
for square in square_generator:
print(square) # 输出: 0, 1, 4, 9, 16, 25, 36, 49, 64, 81
# 生成器表达式与列表推导式的区别
# 列表推导式:立即创建完整的列表
square_list = [x*x for x in range(10)]
print(type(square_list)) # 输出: <class 'list'>
# 生成器表达式:创建一个生成器对象,按需生成元素
square_generator = (x*x for x in range(10))
print(type(square_generator)) # 输出: <class 'generator'>
# 内存使用对比
import sys
print(f"列表推导式占用内存: {sys.getsizeof(square_list)} 字节")
print(f"生成器表达式占用内存: {sys.getsizeof(square_generator)} 字节")2.3 高级生成器特性
2.3.1 send(), throw(), 和 close() 方法
生成器对象除了支持__next__()方法外,还支持以下高级方法:
send(value):向生成器发送一个值,并返回下一个yield的值throw(type, value=None, traceback=None):向生成器抛出一个异常close():关闭生成器
让我们看一个使用send()方法的例子:
def echo_generator():
"""一个可以接收和回显值的生成器"""
response = yield "准备接收输入..."
while True:
response = yield f"你输入了: {response}"
# 创建生成器
echo = echo_generator()
# 启动生成器(第一次调用next())
first_response = next(echo)
print(first_response) # 输出: "准备接收输入..."
# 使用send()方法发送值并获取下一个yield的值
response1 = echo.send("Hello")
print(response1) # 输出: "你输入了: Hello"
response2 = echo.send("World")
print(response2) # 输出: "你输入了: World"
# 关闭生成器
echo.close()
# 尝试再次使用已关闭的生成器(会抛出异常)
# echo.send("再次尝试") # 将抛出 StopIteration 异常2.3.2 协程与yield from
在Python 3.3中,引入了yield from语法,它允许一个生成器委托给另一个生成器、迭代器或可迭代对象。这对于构建复杂的生成器非常有用。
def numbers_generator():
"""生成1到5的数字"""
for i in range(1, 6):
yield i
def letters_generator():
"""生成A到E的字母"""
for letter in 'ABCDE':
yield letter
def combined_generator():
"""组合两个生成器"""
print("生成数字:")
yield from numbers_generator()
print("\n生成字母:")
yield from letters_generator()
# 使用组合生成器
for item in combined_generator():
print(item, end=' ')
# 输出:
# 生成数字:
# 1 2 3 4 5
# 生成字母:
# A B C D E⚡ 三、协程与异步编程
3.1 协程的基本概念
协程是一种特殊的函数,它可以在特定点暂停执行,并在稍后恢复执行。在Python中,协程是通过async def语法定义的函数,并使用await语句来暂停执行,等待另一个协程完成。
需要注意的是,Python中的协程有一个演化过程:从早期基于生成器的协程(使用yield和yield from),到Python 3.5引入的基于async/await的现代协程。
3.2 现代协程(async/await)
Python 3.5及以上版本引入了async和await关键字,使协程的定义和使用更加清晰和直观。
import asyncio
async def say_after(delay, what):
"""在延迟指定时间后打印消息"""
await asyncio.sleep(delay)
print(what)
async def main():
"""主协程"""
# 并发执行两个协程
task1 = asyncio.create_task(say_after(1, "Hello"))
task2 = asyncio.create_task(say_after(2, "World"))
print(f"started at {asyncio.get_event_loop().time():.2f}")
# 等待两个任务完成
await task1
await task2
print(f"finished at {asyncio.get_event_loop().time():.2f}")
# 运行主协程
asyncio.run(main())
# 输出类似于:
# started at 1234567.89
# Hello
# World
# finished at 1234569.893.3 异步IO与事件循环
Python的异步编程基于事件循环(Event Loop)模型。事件循环负责调度和执行协程。它维护一个任务队列,不断地从队列中取出任务执行,当任务需要等待(如IO操作)时,它会暂停该任务,执行其他可执行的任务,从而实现并发。
主要的异步IO操作包括:
- 网络IO(HTTP请求、WebSocket连接等)
- 文件IO
- 数据库操作
- 定时操作
让我们看一个使用异步IO进行网络请求的例子:
import asyncio
import aiohttp
async def fetch(session, url):
"""异步获取URL内容"""
async with session.get(url) as response:
return await response.text()
async def main():
"""主协程"""
urls = [
'https://python.org',
'https://github.com',
'https://pypi.org'
]
async with aiohttp.ClientSession() as session:
# 并发执行多个网络请求
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks)
# 处理结果
for i, result in enumerate(results):
print(f"URL {i+1} 的内容长度: {len(result)} 字节")
# 运行主协程
asyncio.run(main())3.4 协程的高级特性
3.4.1 任务与Future
在Python的异步编程中,Task和Future是两个重要的概念:
Task:是Future的子类,表示一个正在运行的协程。你可以使用asyncio.create_task()函数来创建一个任务。Future:表示一个异步操作的最终结果。它是一个低级别原语,通常你不需要直接使用它。
import asyncio
async def slow_operation():
"""模拟一个慢速操作"""
await asyncio.sleep(1)
return "操作完成"
async def main():
"""主协程"""
# 创建任务
task = asyncio.create_task(slow_operation())
# 检查任务状态
print(f"任务是否完成: {task.done()}") # 输出: False
# 等待任务完成并获取结果
result = await task
# 再次检查任务状态
print(f"任务是否完成: {task.done()}") # 输出: True
print(f"任务结果: {result}") # 输出: "操作完成"
# 运行主协程
asyncio.run(main())3.4.2 异步上下文管理器
异步上下文管理器是一种特殊的上下文管理器,它定义了__aenter__()和__aexit__()方法,而不是普通的__enter__()和__exit__()方法。异步上下文管理器可以在async with语句中使用。
import asyncio
class AsyncContextManager:
"""一个简单的异步上下文管理器"""
async def __aenter__(self):
"""进入上下文时调用"""
print("进入异步上下文")
await asyncio.sleep(0.1) # 模拟异步操作
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""退出上下文时调用"""
await asyncio.sleep(0.1) # 模拟异步操作
print("退出异步上下文")
async def main():
"""主协程"""
async with AsyncContextManager() as acm:
print("在异步上下文中")
# 运行主协程
asyncio.run(main())
# 输出:
# 进入异步上下文
# 在异步上下文中
# 退出异步上下文3.4.3 异步迭代器
异步迭代器是一种特殊的迭代器,它定义了__aiter__()和__anext__()方法,而不是普通的__iter__()和__next__()方法。异步迭代器可以在async for语句中使用。
import asyncio
class AsyncCounter:
"""一个异步计数器"""
def __init__(self, limit):
self.limit = limit
self.count = 0
def __aiter__(self):
"""返回自身作为异步迭代器"""
return self
async def __anext__(self):
"""返回下一个元素"""
if self.count >= self.limit:
raise StopAsyncIteration
self.count += 1
await asyncio.sleep(0.1) # 模拟异步操作
return self.count
async def main():
"""主协程"""
# 使用异步迭代器
async for i in AsyncCounter(5):
print(i) # 输出: 1, 2, 3, 4, 5
# 运行主协程
asyncio.run(main())💼 四、实际应用案例
4.1 生成器应用案例
4.1.1 处理大规模数据
生成器非常适合处理大规模数据,因为它们可以按需生成数据,而不需要一次性加载所有数据到内存中。
def read_large_file(file_path, chunk_size=1024):
"""按块读取大文件"""
with open(file_path, 'r') as file:
while True:
chunk = file.read(chunk_size)
if not chunk:
break
yield chunk
# 使用生成器处理大文件
for chunk in read_large_file('large_file.txt'):
process_chunk(chunk) # 处理每个数据块4.1.2 数据处理管道
生成器可以组合成数据处理管道,每个生成器负责一个特定的数据处理步骤。
def filter_lines(file_path, keyword):
"""过滤包含特定关键字的行"""
with open(file_path, 'r') as file:
for line in file:
if keyword in line:
yield line.strip()
def transform_lines(lines, transformation):
"""对行进行转换"""
for line in lines:
yield transformation(line)
def write_results(results, output_file):
"""将结果写入文件"""
with open(output_file, 'w') as file:
for result in results:
file.write(result + '\n')
# 构建数据处理管道
filtered_lines = filter_lines('input.txt', 'python')
transformed_lines = transform_lines(filtered_lines, lambda x: x.upper())
write_results(transformed_lines, 'output.txt')4.2 协程应用案例
4.2.1 异步Web服务器
协程非常适合构建高性能的Web服务器,因为它可以同时处理多个请求,而不需要为每个请求创建一个新的线程或进程。
import asyncio
from aiohttp import web
async def handle(request):
"""处理HTTP请求"""
name = request.match_info.get('name', "Anonymous")
text = f"Hello, {name}!"
return web.Response(text=text)
async def init_app():
"""初始化Web应用"""
app = web.Application()
app.router.add_get('/', handle)
app.router.add_get('/{name}', handle)
return app
# 运行Web服务器
web.run_app(init_app())4.2.2 并发API请求
协程可以用来并发执行多个API请求,大大提高程序的执行效率。
import asyncio
import aiohttp
import time
async def fetch_data(session, url):
"""异步获取URL数据"""
async with session.get(url) as response:
return await response.json()
async def main():
"""主协程"""
urls = [
'https://jsonplaceholder.typicode.com/posts/1',
'https://jsonplaceholder.typicode.com/posts/2',
'https://jsonplaceholder.typicode.com/posts/3',
'https://jsonplaceholder.typicode.com/posts/4',
'https://jsonplaceholder.typicode.com/posts/5',
]
start_time = time.time()
async with aiohttp.ClientSession() as session:
# 并发执行多个API请求
tasks = [fetch_data(session, url) for url in urls]
results = await asyncio.gather(*tasks)
# 处理结果
for i, result in enumerate(results):
print(f"URL {i+1} 的标题: {result['title']}")
end_time = time.time()
print(f"总耗时: {end_time - start_time:.2f} 秒")
# 运行主协程
asyncio.run(main())🎯 互动练习
练习1:实现一个无限序列生成器
任务:实现一个生成斐波那契数列的生成器函数fibonacci(),它可以生成无限长的斐波那契数列。
def fibonacci():
"""生成无限长的斐波那契数列"""
# 你的代码这里
# 测试代码
fib = fibonacci()
for _ in range(10): # 只获取前10个斐波那契数
print(next(fib), end=' ')
# 预期输出: 0 1 1 2 3 5 8 13 21 34练习2:实现异步定时器
任务:实现一个异步定时器函数async_timer(),它可以按照指定的时间间隔执行指定的回调函数。
import asyncio
async def async_timer(interval, callback, max_times=None):
"""异步定时器
按照指定的时间间隔执行回调函数
max_times: 最大执行次数,None表示无限执行
"""
# 你的代码这里
# 测试代码
async def main():
count = 0
def on_timer():
nonlocal count
count += 1
print(f"定时器触发第 {count} 次")
# 每0.5秒执行一次on_timer,最多执行5次
await async_timer(0.5, on_timer, 5)
print("定时器测试完成")
asyncio.run(main())练习3:实现异步数据处理管道
任务:使用协程实现一个简单的数据处理管道,包括:读取文件、处理数据、写入结果三个步骤。
import asyncio
import aiofiles
async def read_file(file_path):
"""异步读取文件"""
# 你的代码这里
async def process_data(data):
"""异步处理数据"""
# 你的代码这里(例如:计算单词频率)
async def write_results(results, output_file):
"""异步写入结果"""
# 你的代码这里
async def main():
"""主协程"""
data = await read_file('input.txt')
results = await process_data(data)
await write_results(results, 'output.txt')
print("数据处理完成")
asyncio.run(main())📝 小结
在本章中,我们深入探讨了Python中处理数据流和异步操作的强大工具:生成器、迭代器和协程。这些特性不仅可以使你的代码更高效、更优雅,还能帮助你处理大规模数据和构建高性能的异步应用。
主要内容回顾:
- 迭代器:是实现了
__iter__()和__next__()方法的对象,可以使用for循环遍历。 - 生成器:是一种特殊的迭代器,使用
yield语句返回值,可以按需生成数据。 - 协程:是可以暂停和恢复执行的函数,使用
async def和await语句定义和使用,是Python异步编程的基础。
这些特性在实际项目中有广泛的应用,特别是在处理大规模数据、构建高性能Web服务器、进行并发API请求等场景中。掌握这些特性,可以让你的Python编程技能更上一层楼。
在下一章节中,我们将学习Python中的元编程和反射,这是Python中最强大、最灵活的特性之一,也是高级Python编程的重要组成部分。
📚 推荐资源
官方文档:
经典书籍:
- 《流畅的Python》 by Luciano Ramalho(第14章到第18章详细介绍了迭代器、生成器和协程)
- 《Python Cookbook》 by David Beazley & Brian K. Jones(第4章包含了许多关于迭代器和生成器的实用技巧)
网络资源:




