33·模块七:面向对象编程高级

生成器(Generators)与 yield

generatorsyielditeration

第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. 常见错误

  1. 忘记 yield 的返回值:在生成器内部,yield 表达式本身也可以有返回值(通过 send 方法),但对于初学者,最常见的错误是混淆 yieldreturn。函数中的 return 会结束生成器,而 yield 只是暂停。

  2. 误用 next():对一个已经耗尽(抛出 StopIteration)的生成器再次调用 next(),会继续抛出异常。应该使用 try-except 捕获,或者直接使用 for 循环。

  3. 无限循环生成器没有终止条件:如示例中的 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 的代码。下一课,我们将学习装饰器,它将会利用本节课学习的函数作为第一类对象的特性。

练习编辑器

python
Loading...

继续学习

完成本课后,建议继续学习下一课「装饰器基础(函数包装)」 以巩固所学知识。