第33课:生成器(Generators)与 yield
课程:Python 编程:从零基础到精通 模块:模块七:面向对象编程 难度:Advanced 标签:generators, yield, iteration
1. 学习目标
完成本课学习后,你将能够:
- 理解生成器的基本概念及其在内存效率上的优势。
- 掌握使用
yield关键字创建生成器函数。 - 对比并区分生成器与迭代器的异同与应用场景。
- 编写生成器表达式(Generator Expressions)。
- 应用生成器处理大规模数据序列或无限序列。
2. 核心概念
什么是生成器?
想象一下,你需要一个包含一百万个数字的列表。传统做法是创建一个包含所有数字的列表,这会立即占用大量内存。生成器(Generator)则是一种特殊的迭代器,它不会一次性生成所有数据,而是"惰性计算"——在需要时才逐个计算并"吐出"下一个值。这就像一条流水线:数据在需要的时候才被生产出来,而不是提前存满整个仓库。
yield 关键字
yield 是生成器函数的核心。当一个函数包含 yield 语句时,它就不再是一个普通函数,而是一个生成器函数。调用它时,函数体不会立即执行,而是返回一个生成器对象。
- 每次调用生成器的
__next__()方法(或通过next()内置函数)时,函数体开始执行,直到遇到第一个yield语句。 yield会"暂停"函数,并"产出"(yield)一个值。- 下次调用
next()时,函数从上次暂停的地方(即yield语句之后)继续执行,直到遇到下一个yield或函数结束。
3. 代码示例
示例1:基础生成器函数
def count_up_to(n):
"""一个简单的生成器,从1数到n"""
i = 1
while i <= n:
yield i # 产出当前值 i,并暂停
i += 1 # 下次从这里继续执行
# 创建生成器对象(函数体并未执行)
gen = count_up_to(5)
print(next(gen)) # 输出: 1
print(next(gen)) # 输出: 2
print(next(gen)) # 输出: 3
# 也可以在for循环中使用,它会自动处理迭代协议
for number in count_up_to(5):
print(number, end=' ') # 输出: 1 2 3 4 5
print()
示例2:斐波那契数列生成器
def fibonacci_gen(limit=None):
"""生成斐波那契数列的生成器。可指定最大值限制。"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 如果设置了限制,且下一个值大于限制,则停止
if limit is not None and a > limit:
return # 生成器函数执行 return 会触发 StopIteration
# 生成斐波那契数列中不超过100的数字
fib_gen = fibonacci_gen(limit=100)
fib_list = list(fib_gen)
print(fib_list) # 输出: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
示例3:生成器表达式(Generator Expression)
生成器表达式与列表推导式类似,但使用圆括号 (),返回的是生成器对象。
# 列表推导式:一次性创建整个列表
squares_list = [x**2 for x in range(10)]
print(type(squares_list)) # <class 'list'>
# 生成器表达式:惰性生成
squares_gen = (x**2 for x in range(10))
print(type(squares_gen)) # <class 'generator'>
# 按需获取值
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
# 常用于函数参数,可以节省内存
print(sum(x**2 for x in range(1000000))) # 直接传递生成器表达式,不创建中间列表
4. 实践练习
练习1:创建一个单词生成器
要求:编写一个生成器函数 word_generator(sentence),接收一个句子(字符串),按空格拆分后,逐个产出其中的单词。
预期输出:
for word in word_generator("Python generators are powerful"):
print(word, end=' ')
# 输出: Python generators are powerful
练习2:实现一个范围生成器
要求:不使用内置的 range 函数,实现一个类似的生成器函数 my_range(start, stop, step)。它应该能处理浮点数步长。
提示:注意处理 step 为负数或零的情况。
预期输出:
for i in my_range(0, 5, 0.5):
print(i, end=' ') # 输出: 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5
练习3:文件大行处理器(高级)
要求:假设有一个巨大的文本文件 large_log.txt,每一行是一个日志条目。编写一个生成器 read_large_file(file_path),逐行读取文件,但只产出包含 "ERROR" 关键字的行。这样可以避免一次性将整个文件加载到内存。
预期输出:
for error_line in read_large_file("large_log.txt"):
print(error_line.strip())
# 将只打印所有包含"ERROR"的行
5. 常见错误
-
忘记
yield的返回值:在生成器内部,yield表达式本身也可以有返回值(通过send方法),但对于初学者,最常见的错误是混淆yield和return。函数中的return会结束生成器,而yield只是暂停。 -
误用
next():对一个已经耗尽(抛出StopIteration)的生成器再次调用next(),会继续抛出异常。应该使用try-except捕获,或者直接使用for循环。 -
无限循环生成器没有终止条件:如示例中的
fibonacci_gen,如果作为无限生成器使用,务必小心。要么在内部设置退出条件(如limit参数),要么在外部循环中手动break,否则会导致无限循环。
# 错误示例:无限生成器使用不当
def infinite_counter():
n = 0
while True:
yield n
n += 1
# 这会创建一个无限循环,实际编程中应避免
# for i in infinite_counter():
# print(i) # 程序会一直运行下去
# 正确做法:结合条件判断
counter = infinite_counter()
for _ in range(5):
print(next(counter), end=' ') # 输出: 0 1 2 3 4
print()
6. 小结
- 生成器是什么:一个使用
yield关键字返回数据的迭代器。它"惰性计算"和"产出"值,而不是一次性存储所有值。 - 核心优势:内存高效。特别适用于处理大型数据集、流式数据或无限序列。
yield的作用:暂停函数执行,保存当前状态(包括变量、执行位置),并返回一个值给调用者。下次调用时从暂停处恢复。- 生成器表达式:
(expression for item in iterable)的语法,是创建简单生成器的快捷方式。 - 与迭代器的关系:生成器是迭代器的一种便捷实现方式。每个生成器对象都自动实现了
__iter__()和__next__()方法(迭代器协议),我们无需手动编写这些方法。 - 关键提醒:生成器只能遍历一次。遍历结束后,若需重新使用,必须重新调用生成器函数创建一个新的生成器对象。
生成器是 Python 中强大且优雅的工具,理解了 yield 的工作原理,你将能编写出更高效、更 Pythonic 的代码。下一课,我们将学习装饰器,它将会利用本节课学习的函数作为第一类对象的特性。