Skip to content

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() 抛出 StopIterationfor 循环捕获这个异常后结束。

二、生成器函数

函数里用 yield 代替 return,就变成了生成器函数:

python
def countdown(n):
    while n > 0:
        yield n
        n -= 1

for num in countdown(5):
    print(num)
# 5, 4, 3, 2, 1

yieldreturn 的区别:

  • 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.0

send() 把值传进生成器,赋值给 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 委托子生成器。