跳到主要内容

生成器与迭代器

问题

Python 中迭代器协议是什么?生成器的原理和应用场景?yieldreturn 的区别?

答案

迭代器协议

任何实现了 __iter____next__ 方法的对象都是迭代器:

class Counter:
"""自定义迭代器:从 start 数到 stop"""
def __init__(self, start: int, stop: int):
self.current = start
self.stop = stop

def __iter__(self):
return self # 迭代器返回自身

def __next__(self):
if self.current >= self.stop:
raise StopIteration # 迭代结束
value = self.current
self.current += 1
return value

for i in Counter(1, 4):
print(i) # 1, 2, 3

可迭代对象 vs 迭代器

概念协议说明
可迭代(Iterable)__iter__list, str, dict
迭代器(Iterator)__iter__ + __next__有状态,一次性消费
lst = [1, 2, 3]           # 可迭代对象
it = iter(lst) # 获取迭代器
print(next(it)) # 1
print(next(it)) # 2

生成器函数

使用 yield 的函数会变成生成器函数,调用它返回一个生成器对象(特殊的迭代器):

def fibonacci():
"""无限斐波那契数列"""
a, b = 0, 1
while True:
yield a # 暂停执行,产出值
a, b = b, a + b

# 惰性求值:不会一次性生成所有值
fib = fibonacci()
print(next(fib)) # 0
print(next(fib)) # 1
print(next(fib)) # 1
print(next(fib)) # 2

# 取前 10 个
from itertools import islice
print(list(islice(fibonacci(), 10)))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

yield vs return

对比yieldreturn
函数类型生成器函数普通函数
执行方式暂停并产出值,下次从暂停处继续终止函数,返回值
返回值生成器对象普通值
内存惰性求值,占用极少内存一次性计算全部

生成器表达式

# 列表推导式 — 一次性创建所有元素
squares_list = [x**2 for x in range(1_000_000)] # 占用大量内存

# 生成器表达式 — 惰性求值
squares_gen = (x**2 for x in range(1_000_000)) # 几乎不占内存

# 常用于大数据量场景
total = sum(x**2 for x in range(1_000_000)) # 直接传给函数

send、throw 和 close

def accumulator():
"""接收外部发送的值"""
total = 0
while True:
value = yield total # yield 可以接收 send 的值
if value is None:
break
total += value

gen = accumulator()
next(gen) # 启动生成器,走到第一个 yield → 0
print(gen.send(10)) # 发送 10,total=10 → 10
print(gen.send(20)) # 发送 20,total=30 → 30
print(gen.send(5)) # 发送 5,total=35 → 35

yield from

yield from 委托给子生成器,简化嵌套迭代:

def flatten(nested):
"""递归展开嵌套列表"""
for item in nested:
if isinstance(item, list):
yield from flatten(item) # 委托给子生成器
else:
yield item

data = [1, [2, [3, 4]], 5, [6]]
print(list(flatten(data))) # [1, 2, 3, 4, 5, 6]

实际应用场景

1. 读取大文件

def read_large_file(file_path: str):
"""逐行读取大文件,不会一次性加载到内存"""
with open(file_path, "r") as f:
for line in f:
yield line.strip()

# 处理 10GB 的日志文件也不会 OOM
for line in read_large_file("huge_log.txt"):
if "ERROR" in line:
print(line)

2. 数据管道

def read_csv(path):
for line in open(path):
yield line.strip().split(",")

def filter_active(rows):
for row in rows:
if row[2] == "active":
yield row

def extract_names(rows):
for row in rows:
yield row[0]

# 管道组合:数据流式处理,内存恒定
pipeline = extract_names(filter_active(read_csv("users.csv")))
for name in pipeline:
print(name)

常见面试问题

Q1: 生成器和普通函数的区别?

答案

普通函数用 return 返回值并结束;生成器函数用 yield 暂停并产出值,保留执行状态。生成器是惰性求值的,适合处理大数据量或无限序列。

Q2: list(range(1000000))range(1000000) 的区别?

答案

  • range(1000000) 是一个惰性序列对象,不会立即生成所有数字,内存占用极小
  • list(range(1000000)) 会立即创建包含 100 万个整数的列表,占用约 8MB 内存

Python 3 的 range 是惰性的(Python 2 的 range 返回列表,xrange 才是惰性的)。

Q3: 如何让一个对象可以用 for 循环遍历?

答案

实现 __iter__ 方法,返回一个迭代器:

class NumberRange:
def __init__(self, start, end):
self.start = start
self.end = end

def __iter__(self):
# 方法一:返回生成器(推荐,简洁)
current = self.start
while current < self.end:
yield current
current += 1

for n in NumberRange(1, 5):
print(n) # 1, 2, 3, 4

Q4: 生成器的内存优势体现在哪里?

答案

import sys

# 列表:一次性存储所有元素
lst = [i for i in range(1_000_000)]
print(sys.getsizeof(lst)) # ~8,000,056 bytes (约 8MB)

# 生成器:只保存执行状态
gen = (i for i in range(1_000_000))
print(sys.getsizeof(gen)) # ~200 bytes (约 200B)

内存差距在百万倍量级。生成器适合大文件处理、流式数据、无限序列等场景。

Q5: yield from 和普通 for + yield 有什么区别?

答案

功能上等价,但 yield from 还能:

  1. 自动处理 send()throw()close() 的转发
  2. 获取子生成器的返回值
def sub_gen():
yield 1
yield 2
return "done"

def main_gen():
result = yield from sub_gen() # 获取子生成器的 return 值
print(f"子生成器返回: {result}")

list(main_gen()) # [1, 2] + 打印 "子生成器返回: done"

相关链接