一、什么是生成器函数?
生成器函数是一种特殊的函数,它使用 yield 关键字而不是 return 来返回值。当调用生成器函数时,它不会立即执行,而是返回一个生成器对象(Generator Object)。每次调用 next() 或在 for 循环中迭代时,函数会从上次 yield 的位置继续执行,直到遇到下一个 yield 或函数结束。
defcount_down(n):
"""简单的倒计时生成器"""
while n > 0:
yield n
n -= 1
gen = count_down(5)
print(next(gen)) # 5
print(next(gen)) # 4
print(list(gen)) # [3, 2, 1]
二、为什么需要生成器函数?
- • 惰性求值:只在需要时生成值,不预先计算所有结果。
- • 代码简洁:相比手动实现迭代器类,生成器函数代码量少得多。
- • 状态保持:自动保存函数执行状态,无需手动管理。
三、生成器函数的基本语法
3.1 一个最简单的生成器
defsimple_generator():
print("第一次调用")
yield1
print("第二次调用")
yield2
print("第三次调用")
yield3
gen = simple_generator()
print(next(gen)) # 第一次调用 \n 1
print(next(gen)) # 第二次调用 \n 2
print(next(gen)) # 第三次调用 \n 3
print(next(gen)) # StopIteration
3.2 在循环中使用生成器
defgenerate_numbers(n):
for i inrange(n):
yield i * 2
for num in generate_numbers(5):
print(num) # 0, 2, 4, 6, 8
3.3 无限生成器
definfinite_counter(start=0):
"""无限递增计数器"""
whileTrue:
yield start
start += 1
counter = infinite_counter()
for _ inrange(10):
print(next(counter)) # 0, 1, 2, ..., 9
四、yield 的执行原理
生成器函数的执行过程:
- 1. 调用生成器函数时,返回生成器对象,函数体不会立即执行。
- 2. 第一次调用
next(),函数执行到第一个 yield,返回 yield 后的值,函数暂停在该位置。 - 3. 再次调用
next(),函数从上次暂停的位置继续执行,直到遇到下一个 yield 或函数结束。 - 4. 如果函数执行完毕(没有更多
yield),会抛出 StopIteration 异常。
deftrace_generator():
print("函数开始")
yield"A"
print("A 之后")
yield"B"
print("B 之后")
yield"C"
print("函数结束")
gen = trace_generator()
print(next(gen)) # 函数开始 \n A
print(next(gen)) # A 之后 \n B
print(next(gen)) # B 之后 \n C
print(next(gen)) # 函数结束 \n StopIteration
五、生成器与列表的对比
import sys
# 列表:一次性生成所有元素,占用内存
defget_squares_list(n):
return [i**2for i inrange(n)]
# 生成器:惰性生成,节省内存
defget_squares_gen(n):
for i inrange(n):
yield i**2
# 内存对比
n = 1000000
list_squares = get_squares_list(n)
gen_squares = get_squares_gen(n)
print(f"列表内存: {sys.getsizeof(list_squares)} 字节") # 很大
print(f"生成器内存: {sys.getsizeof(gen_squares)} 字节") # 很小
六、生成器的方法
生成器对象除了支持 next(),还有以下方法:
6.1 send(value) —— 向生成器发送值
send() 方法可以向生成器内部传递一个值,该值会成为当前 yield 表达式的返回值。
defaccumulator():
total = 0
whileTrue:
value = yield total # yield 可以接收外部传入的值
if value isNone:
break
total += value
acc = accumulator()
next(acc) # 必须先启动生成器,等价于 acc.send(None)
print(acc.send(10)) # 10
print(acc.send(20)) # 30
print(acc.send(30)) # 60
6.2 throw(type, value=None, traceback=None) —— 向生成器抛出异常
defgenerator_with_exception():
try:
yield1
yield2
except ValueError:
yield"捕获到 ValueError"
yield3
gen = generator_with_exception()
print(next(gen)) # 1
print(gen.throw(ValueError)) # 捕获到 ValueError
print(next(gen)) # 3
6.3 close() —— 关闭生成器
defgenerator():
try:
yield1
yield2
yield3
finally:
print("生成器被关闭")
gen = generator()
print(next(gen)) # 1
gen.close() # 生成器被关闭
# print(next(gen)) # StopIteration
七、yield from 委托生成器
yield from 可以将迭代任务委托给另一个生成器或可迭代对象。
defgenerator_a():
yield1
yield2
yield3
defgenerator_b():
yield0
yieldfrom generator_a() # 委托给 generator_a
yield4
for value in generator_b():
print(value, end=' ') # 0, 1, 2, 3, 4
7.1 扁平化嵌套列表
defflatten(nested):
"""扁平化嵌套列表"""
for item in nested:
ifisinstance(item, list):
yieldfrom flatten(item) # 递归委托
else:
yield item
nested = [1, [2, [3, 4], 5], 6, [7, 8]]
result = list(flatten(nested))
print(result) # [1, 2, 3, 4, 5, 6, 7, 8]
八、实战案例
8.1 读取大文件(逐行处理)
defread_large_file(file_path):
"""生成器,逐行读取大文件,不占用内存"""
withopen(file_path, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()
# 处理几 GB 的日志文件
for line in read_large_file('huge_log.txt'):
if'ERROR'in line:
print(line)
8.2 斐波那契数列生成器
deffibonacci():
"""无限生成斐波那契数列"""
a, b = 0, 1
whileTrue:
yield a
a, b = b, a + b
fib = fibonacci()
for _ inrange(10):
print(next(fib), end=' ') # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
8.3 分页数据获取
defpaginated_fetch(page_size=10):
"""模拟从数据库分页获取数据"""
page = 0
whileTrue:
offset = page * page_size
# 模拟数据库查询
data = list(range(offset + 1, offset + page_size + 1))
ifnot data or data[0] > 100: # 假设只有 100 条数据
break
yield data
page += 1
for page in paginated_fetch(15):
print(f"第 {page[0]//15 + 1} 页: {page}")
8.4 流式数据处理管道
defread_numbers():
"""产生数字流"""
for i inrange(1, 11):
yield i
deffilter_even(source):
"""过滤偶数"""
for num in source:
if num % 2 == 0:
yield num
defsquare(source):
"""计算平方"""
for num in source:
yield num ** 2
defsum_stream(source):
"""求和"""
total = 0
for value in source:
total += value
yield total
# 构建处理管道
pipeline = sum_stream(square(filter_even(read_numbers())))
for result in pipeline:
print(result) # 4, 20, 56, 120, 220
九、生成器 vs 迭代器 vs 可迭代对象
from collections.abc import Iterator, Iterable
defmy_generator():
yield1
yield2
gen = my_generator()
print(isinstance(gen, Iterable)) # True
print(isinstance(gen, Iterator)) # True(生成器是迭代器的一种)
# 列表是可迭代对象,但不是迭代器
lst = [1, 2, 3]
print(isinstance(lst, Iterable)) # True
print(isinstance(lst, Iterator)) # False
十、注意事项
- 2.
send() 方法需要先启动生成器:通常先调用一次 next() 或 send(None)。 - 3.
yield from 可以简化委托逻辑,但也要注意性能。 - 4. 生成器函数中的
return:在生成器函数中,return 会触发 StopIteration,可以附带返回值(Python 3.3+)。defgenerator_with_return():
yield1
yield2
return"完成"
gen = generator_with_return()
print(list(gen)) # [1, 2]
# 返回值可以通过捕获 StopIteration 获得,但通常不这样做
- 5. 不要在大数据场景下使用
list(generator):这会一次性加载所有数据到内存,失去生成器的优势。
十一、总结
- • 生成器函数是使用
yield 的函数,返回生成器对象。 - • 支持
send()、throw()、close():可以实现双向通信和异常处理。 - •
yield from 用于委托给另一个生成器,简化嵌套生成。 - • 适用于:处理大数据流、无限序列、分页获取、数据管道等场景。
生成器是 Python 中非常强大的特性,掌握它可以让你的代码更加 Pythonic、高效且易于理解。