Appearance
31|生成器与迭代器
处理大量数据时,一次性把所有内容加载到内存可能不现实。一个几 GB 的日志文件,用 readlines() 会耗尽内存。生成器提供了一种"按需计算"的机制——用到哪个值就算哪个,不需要提前算完。
一、迭代器
迭代器是支持 __iter__() 和 __next__() 的对象。for 循环本质上就是在调用迭代器的 __next__():
python
nums = [1, 2, 3]
it = iter(nums)
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
print(next(it)) # StopIteration 异常iter() 把可迭代对象转成迭代器,next() 取下一个值。值取完后再调用 next() 抛出 StopIteration,for 循环捕获这个异常后结束。
二、生成器函数
函数里用 yield 代替 return,就变成了生成器函数:
python
def countdown(n):
while n > 0:
yield n
n -= 1
for num in countdown(5):
print(num)
# 5, 4, 3, 2, 1yield 和 return 的区别:
return结束函数,返回值给调用方yield暂停函数,把值交给调用方,下次从暂停处继续
生成器函数返回的是一个生成器对象,不是直接执行函数体:
python
gen = countdown(3)
type(gen) # <class 'generator'>
print(next(gen)) # 3
print(next(gen)) # 2
print(next(gen)) # 1
print(next(gen)) # StopIteration三、生成器表达式
和列表推导式类似,但用圆括号,结果是生成器:
python
# 列表推导式:一次性算完,全放内存
squares = [x * x for x in range(1_000_000)]
# 生成器表达式:用到才算
squares = (x * x for x in range(1_000_000))
print(next(squares)) # 0
print(next(squares)) # 1生成器表达式适合传给只遍历一次的函数:
python
total = sum(x * x for x in range(1000)) # 不需要先建列表
has_big = any(x > 500 for x in range(1000)) # 找到即停四、读取大文件
生成器最适合处理大文件——逐行读取,不占用大量内存:
python
def read_lines(path):
with open(path, encoding="utf-8") as f:
for line in f:
yield line.strip()
for line in read_lines("big_log.txt"):
if "ERROR" in line:
print(line)这个生成器每次只从文件读一行,处理完再读下一行。无论文件多大,内存里始终只有当前行。
五、管道处理
多个生成器可以串联,形成处理管道:
python
def read_lines(path):
with open(path) as f:
for line in f:
yield line.strip()
def filter_errors(lines):
for line in lines:
if "ERROR" in line:
yield line
def extract_timestamp(lines):
for line in lines:
parts = line.split(" ")
yield parts[0] # 假设时间戳是第一个字段
# 管道:读取 → 过滤 → 提取
timestamps = extract_timestamp(filter_errors(read_lines("app.log")))
for ts in timestamps:
print(ts)每个生成器只做一件事,通过 yield 把结果传给下一个。数据像水流一样经过管道,不需要在中间环节存下全部数据。
六、send 和状态
生成器不仅可以产出值,还可以接收值:
python
def running_average():
total = 0
count = 0
while True:
value = yield (total / count if count else 0)
total += value
count += 1
avg = running_average()
next(avg) # 预激生成器,执行到第一个 yield
avg.send(10) # 10.0
avg.send(20) # 15.0
avg.send(30) # 20.0send() 把值传进生成器,赋值给 yield 表达式。这个特性在协程和状态机中用到,日常脚本中较少见。
七、yield from
在生成器中委托给另一个生成器:
python
def sub_generator():
yield 1
yield 2
def main_generator():
yield "start"
yield from sub_generator()
yield "end"
print(list(main_generator()))
# ['start', 1, 2, 'end']yield from 把子生成器的值逐个产出,同时处理子生成器的 send() 和 return。
记忆锚点:生成器用 yield 产出值,暂停后从暂停处继续;生成器表达式 (x for x in ...) 省内存;适合处理大文件和管道处理;next() 取下一个值,StopIteration 表示结束;send() 向生成器传值;yield from 委托子生成器。