在 Python 编程中,当我们需要处理大量数据或无限序列时,一次性将所有数据加载到内存中可能非常低效甚至不可能。生成器(Generators)提供了一种优雅的解决方案,它允许我们按需生成值,从而节省内存并提高性能。本教程将带你从零开始理解生成器,并通过实际示例掌握它的用法。
- 什么是生成器?
生成器是一种特殊的迭代器,它使用 yield 语句来产生一系列值。与普通函数不同,生成器函数不会一次性返回所有结果,而是在每次调用时暂停并返回一个值,直到下一次迭代继续执行。
普通函数 vs. 生成器函数
python
普通函数:返回一个列表,占用内存
def squares(n):
result = []
for i in range(n):
result.append(i ** 2)
return result
生成器函数:使用 yield,逐个产生值
def squares_gen(n):
for i in range(n):
yield i ** 2
调用普通函数会立即计算所有值并返回列表;而调用生成器函数会返回一个生成器对象,可以迭代它来逐个获取值。
- 生成器的工作原理
生成器函数遇到 yield 时会暂停执行,并将 yield 后的值返回给调用者。下次调用生成器的 next() 方法(或使用 next() 函数)时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 或函数结束。
python
def count_up_to(n):
i = 0
while i < n:
yield i
i += 1
创建生成器对象
gen = count_up_to(3)
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 引发 StopIteration
- 生成器表达式
生成器表达式类似于列表推导式,但使用圆括号而非方括号,它返回一个生成器对象,而不是列表。
python
列表推导式:立即生成所有值
squares_list = [x**2 for x in range(10)]
生成器表达式:惰性求值
squares_gen = (x**2 for x in range(10))
print(squares_list) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
print(squares_gen) # at 0x...>
迭代生成器
for val in squares_gen:
print(val, end=' ') # 0 1 4 9 16 25 36 49 64 81
生成器表达式在只需要遍历一次数据时非常高效,因为它不会一次性占用大量内存。
- 为什么使用生成器?
4.1 节省内存
当处理大型数据集(如读取大文件、处理无限序列)时,生成器只保留当前值,而不是整个序列。
python
读取大文件时,逐行处理,避免将整个文件加载到内存
def read_large_file(file_path):
with open(file_path, 'r') as f:
for line in f:
yield line.strip()
使用生成器逐行处理
for line in read_large_file('huge_log.txt'):
process(line) # 每次只处理一行
4.2 延迟计算
生成器支持惰性求值,仅在需要时才计算值,这在处理无限序列时特别有用。
python
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib), end=' ') # 0 1 1 2 3 5 8 13 21 34
4.3 代码简洁
使用生成器可以避免维护复杂的状态变量,使代码更易读。
- 生成器的高级用法
5.1 与 send() 交互
生成器不仅可以产生值,还可以接收外部传入的值,通过 send() 方法实现双向通信。
python
def echo():
while True:
received = yield
print(f"Received: {received}")
gen = echo()
next(gen) # 启动生成器,执行到第一个 yield
gen.send("Hello") # Received: Hello
gen.send("World") # Received: World
5.2 使用 throw() 和 close()
throw():在生成器内部引发异常
close():终止生成器
python
def my_gen():
try:
yield 1
yield 2
except GeneratorExit:
print("Generator closed")
except ValueError:
print("ValueError occurred")
g = my_gen()
print(next(g)) # 1
g.throw(ValueError) # 输出 "ValueError occurred"
生成器被终止
- 实际应用案例
案例1:分页处理数据库查询
当数据库查询结果集很大时,可以使用生成器按页加载数据。
python
def paginated_query(query, page_size=100):
offset = 0
while True:
page = execute_query(query + f" LIMIT {page_size} OFFSET {offset}")
if not page:
break
for row in page:
yield row
offset += page_size
使用
for record in paginated_query("SELECT * FROM large_table"):
process(record)
案例2:流式处理日志文件
实时解析日志文件,提取关键信息。
python
def tail_log(file_path):
import time
with open(file_path, 'r') as f:
f.seek(0, 2) # 移动到文件末尾
while True:
line = f.readline()
if not line:
time.sleep(0.1)
continue
yield line.strip()
for line in tail_log('/var/log/syslog'):
if 'ERROR' in line:
print(line)
- 总结
生成器是 Python 中处理数据流的强大工具,具有以下优点:
内存高效:无需一次性存储所有数据
惰性求值:仅在需要时计算
可组合性:可以轻松构建数据管道
代码清晰:简化迭代逻辑
掌握生成器后,你可以更优雅地处理大数据、无限序列和异步操作。现在,尝试用生成器改写你代码中的循环和列表推导,体验它的魅力吧!
练习:
写一个生成器,产生前 n 个素数。
使用生成器表达式计算 1 到 100 中偶数的平方和。
实现一个简单的协程,模拟一个计数器,可以接收递增步长。
提示:生成器是迭代器的一种,但迭代器不一定是生成器。理解 yield 关键字是掌握生成器的关键。