欢迎来到 Python 学习计划的第 66 天!🎉
昨天我们深入学习了 迭代器协议(__iter__ 与 __next__),知道了如何自定义迭代器类。但每次都要写一个类来实现迭代器太繁琐了!
今天我们将学习 Python 提供的更简洁的方式——生成器(Generator)。只需一个函数和一个 yield 关键字,就能轻松创建迭代器。这是理解异步编程(Asyncio)的前置知识!
一、什么是生成器?
1. 核心定义
生成器 是一种特殊的迭代器,通过函数和 yield 关键字创建。与普通函数不同,生成器函数不会一次性返回所有结果,而是在每次调用时产出一个值,然后暂停执行,等待下次调用。
2. 生成器函数 vs 普通函数
特性 | 普通函数 | 生成器函数 |
|---|
关键字 | return
| yield
|
返回值 | 直接返回结果 | 返回生成器对象 |
执行方式 | 一次性执行完 | 分步执行,可暂停 |
状态保存 | 不保存局部变量 | 保存局部变量状态 |
内存占用 | 可能很大(列表) | 按需生成,很小 |
# 普通函数:立即计算所有值def get_numbers(): result = [] for i in range(5): result.append(i) return result # 返回 [0, 1, 2, 3, 4]# 生成器函数:按需生成def generate_numbers(): for i in range(5): yield i # 返回生成器对象print(get_numbers()) # [0, 1, 2, 3, 4]print(generate_numbers()) # <generator object ...>
二、yield 关键字详解
1. yield 的作用
- 产出一个值:类似
return,但不会终止函数。 - 暂停执行:函数状态被冻结,局部变量保留。
- 等待唤醒:下次调用
next() 时从暂停处继续执行。
2. 执行流程示例
def demo_generator(): print("第一步") yield 1 print("第二步") yield 2 print("第三步") yield 3 print("结束")gen = demo_generator()# 此时函数还未执行print("调用 next()")print(next(gen)) # 输出:第一步 \n 1print("再次调用 next()")print(next(gen)) # 输出:第二步 \n 2
3. 自动处理 StopIteration
当函数执行完毕(遇到 return 或自然结束),生成器会自动抛出 StopIteration 异常,for 循环会自动捕获。
def countdown(n): while n > 0: yield n n -= 1for num in countdown(3): print(num) # 3, 2, 1 (自动停止)
三、生成器表达式
类似列表推导式,但使用圆括号。这是创建简单生成器的最便捷方式。
# 列表推导式 - 立即创建所有元素squares_list = [x**2 for x in range(10)]# 生成器表达式 - 按需生成squares_gen = (x**2 for x in range(10))print(type(squares_list)) # <class 'list'>print(type(squares_gen)) # <class 'generator'># 遍历生成器for sq in squares_gen: print(sq)
💡 优势:生成器表达式内存占用极小,适合大数据处理。四、高级功能:send(), throw(), close()
生成器不仅是迭代器,还可以作为协程使用,支持双向通信。
1. send() 方法
向生成器发送值,该值成为 yield 表达式的结果。
def accumulator(): total = 0 while True: value = yield total # 接收 send 发送的值 if value is not None: total += valuegen = accumulator()print(next(gen)) # 0 (启动生成器,必须先用 next 或 send(None))print(gen.send(10)) # 10 (total = 0 + 10)print(gen.send(20)) # 30 (total = 10 + 20)
2. throw() 和 close()
在生成器内部抛出异常或关闭生成器。
def controlled_generator(): try: while True: yield "运行中" except ValueError: yield "捕获到 ValueError" finally: print("清理资源")gen = controlled_generator()print(next(gen)) # 运行中print(gen.throw(ValueError)) # 捕获到 ValueErrorgen.close() # 清理资源
3. yield from
委托给另一个生成器,简化嵌套循环。
def sub_generator(): yield 1 yield 2def main_generator(): yield "开始" yield from sub_generator() # 委托 yield "结束"for item in main_generator(): print(item) # 开始,1, 2, 结束
五、OOP 实战应用
1. 类中的生成器方法
class DataProcessor: def __init__(self, data): self.data = data # 实例属性 def filter_even(self): """生成器方法:过滤偶数""" for item in self.data: if item % 2 == 0: yield item def transform(self): """生成器方法:转换数据""" for item in self.filter_even(): # 链式调用生成器 yield item * 10# 使用processor = DataProcessor([1, 2, 3, 4, 5])for value in processor.transform(): print(value) # 20, 40
2. 树的遍历
class TreeNode: def __init__(self, value, children=None): self.value = value self.children = children or [] def traverse(self): """深度优先遍历生成器""" yield self.value for child in self.children: yield from child.traverse() # 递归委托# 构建树tree = TreeNode(1, [ TreeNode(2, [TreeNode(4), TreeNode(5)]), TreeNode(3)])# 遍历for value in tree.traverse(): print(value) # 1, 2, 4, 5, 3
3. 无限序列
class IdGenerator: """ID 生成器类""" _counter = 0 # 类属性:共享计数器 @classmethod def generate_ids(cls, start=0): """类方法生成器:生成无限 ID""" current = start while True: yield current current += 1# 使用id_gen = IdGenerator.generate_ids(1000)print(next(id_gen)) # 1000print(next(id_gen)) # 1001
六、实际应用示例
1. 读取大文件
避免一次性加载整个文件到内存。
def read_large_file(filepath, chunk_size=1024): with open(filepath, "r", encoding="utf-8") as f: while True: chunk = f.read(chunk_size) if not chunk: break yield chunk# 即使文件有 10GB,内存占用也很小for chunk in read_large_file("huge_log.txt"): if "ERROR" in chunk: print(chunk)
2. 数据管道(Pipeline)
构建流式数据处理链,每个步骤都是惰性的。
def read_data(): for i in range(100): yield {"id": i, "value": i * 10}def filter_data(data): for item in data: if item["value"] > 500: yield itemdef transform_data(data): for item in data: yield {**item, "doubled": item["value"] * 2}# 构建管道 - 数据流式通过,不在中间积累pipeline = transform_data(filter_data(read_data()))for record in pipeline: print(record) # 只在遍历时才真正执行计算
3. 斐波那契数列
生成无限序列,按需取值。
def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b# 获取前 10 个fib = fibonacci()for _ in range(10): print(next(fib), end=" ") # 0 1 1 2 3 5 8 13 21 34
七、常见误区与注意事项
1. 一次性消费
生成器只能迭代一次,耗尽后需要重新创建。
gen = (x for x in range(3))print(list(gen)) # [0, 1, 2]print(list(gen)) # [] (已耗尽)# 正确:重新创建gen = (x for x in range(3))
2. 无法获取长度
生成器不支持 len(),也无法随机访问。
gen = (x for x in range(100))# print(len(gen)) # TypeError# print(gen[5]) # TypeError
3. send() 启动
第一次调用必须用 next() 或 send(None) 启动生成器。
def coro(): x = yield print(x)c = coro()# c.send(10) # ❌ TypeError: 需要先用 next 启动next(c) # ✅ 启动c.send(10) # ✅ 发送值
4. 调试困难
无法直接打印所有内容,转为列表会耗尽生成器。
# 调试技巧:仅用于小数据gen = (x for x in range(5))print(list(gen)) # 查看内容,但生成器已耗尽
八、总结
知识点 | 说明 |
|---|
生成器 | 使用 yield 的函数,返回迭代器对象 |
yield | 产出值并暂停,保存局部状态 |
表达式 | (x for x in ...) 简洁创建生成器
|
send() | 向生成器发送值,实现双向通信 |
yield from | 委托给子生成器,简化嵌套 |
一次性 | 生成器只能迭代一次,无法重置 |
内存 | 惰性计算,内存占用固定且很小 |
核心要点
- 生成器是特殊的迭代器,自动实现
__iter__ 和 __next__。 yield 暂停函数,下次调用从暂停处继续。- 生成器表达式 比列表推导式更省内存。
send() 方法 让生成器可作为协程使用。- 注意一次性消费,耗尽后需重新创建。
📌 明日预告:生成器的优势:惰性计算与内存节省
明天我们将进入 并发与迭代模块第三天!
- 主题:生成器的优势:惰性计算与内存节省
- 核心问题:
- 什么是惰性计算(Lazy Evaluation)?
- 生成器比列表节省多少内存?
- 如何处理无限序列?
- 数据管道如何优化内存?
- 何时使用生成器,何时使用列表?
💡 提前思考:
- 如果要处理 10GB 的日志文件,用列表会发生什么?
- 生成器表达式
sum(x**2 for x in range(1000000)) 为什么比列表快? - 惰性计算对性能有什么影响?
掌握生成器函数,是迈向高效 Python 编程的关键一步!明天深入理解内存优化!继续加油!🚀