Python中的生成器

Python中的生成器

本文介绍了Python中的生成器。

YouTube Video

Python中的生成器

概述

Python的生成器是一种迭代器,是高效执行重复处理的强大功能。它们可以让你在处理大量数据时编写内存高效的代码

什么是生成器?

Python中的生成器是一种特殊的函数,使用 yield 关键字定义,每次产生一个值。其特点是在保留当前状态的同时暂停执行,之后可以继续执行。

yield 的基础用法

yield 是一个关键字,返回一个值的同时暂停函数的执行

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • 调用该函数时会返回一个生成器对象,该对象会依次生成值。
  • 如果在没有下一个值时调用 next(),会发生 StopIteration 错误。

next()StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • 通过像这样显式地处理 StopIteration 错误,你可以检测生成器何时结束。

send(value)

send(value) 的调用会恢复生成器的执行,并将 value 发送到 yield 表达式的位置。发送的值在生成器端可以作为 yield 表达式的返回值接收。第一次调用时,send(value) 不能发送除 None 以外的任何值,因此必须使用 next()send(None)

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • 使用 send(10) 时,生成器中的 yield 变成返回 10 的表达式,10 会被赋值给 x

throw()

调用 throw 会恢复生成器的执行,并在暂停的 yield 位置抛出异常。你可以在生成器内部处理异常以继续进行处理。如果异常没有被捕获,会向外传播并终止生成器。

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • 在此代码中,通过调用 throw 向生成器注入异常。在生成器端,异常被处理并返回 recovered

close()

调用 close() 会终止生成器。在生成器内部,你可以使用 finally 进行清理操作。在调用 close() 之后,再调用 next()send() 会引发 StopIteration 错误。

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • 此代码显示,调用 close() 会终止生成器并触发 finally 中的清理过程。

yield from

yield from 是用于委托给子生成器的语法。这是一种在生成器内部调用另一个生成器,并将其所有值传递到外部作用域的简单方法。

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • 这段代码使用 yield from 将子生成器的所有值委托给外部生成器,然后再生成 3

与迭代器的关系

生成器内部实现了 __iter__()__next__(),因此它们是迭代器的一种。因此,它们完全兼容如 for 循环等可迭代操作。

for 循环的结合

在Python中,for 循环内部会自动使用 next() 来获取值。

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

使用这种方式,StopIteration 的处理也是自动完成的。

创建无限生成器

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

可以创建无限循环,但在使用时需要注意。

生成器表达式

生成器表达式使用圆括号编写,可以用类似列表推导式的语法定义生成器。

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

与列表推导式不同,它们不会一次性将所有元素加载到内存,因此更加节省内存。

生成器中的错误处理

生成器内部可能会发生异常。在这种情况下,你可以像普通的Python代码一样使用 try-except

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

在这个例子中,对除零错误进行了适当的异常处理。

生成器的堆栈跟踪

如果生成器内部发生异常,则在生成器恢复时会抛出该异常。

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • 该生成器首先返回 1。恢复时抛出的错误会被捕获,并显示为错误信息。

生成器的使用示例

逐行读取文件(适合大型文件)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • 该函数通过迭代器逐行读取文本文件,去除每行的空白字符,并作为生成器返回,从而可以低内存消耗地处理大文件。

斐波那契数列生成器

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • 该代码使用生成器顺序生成小于上限的斐波那契数,并用 for 循环输出。

应用场景

生成器还可以用于以下场景。

  • 对大型CSV或日志文件进行顺序处理
  • API分页处理
  • 处理流式数据(如Kafka、物联网设备)

总结

概念 关键点
yield 暂停并返回一个值
生成器函数 包含 yield 的函数,调用时返回一个迭代器
优点 节省内存,非常适合处理大型数据集
生成器表达式 允许像 (x for x in iterable) 这样的简洁语法

通过使用生成器,你可以高效地处理大型数据集,同时节省内存并保持代码简洁。

您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。

YouTube Video