人人都会AI编程

8.6 迭代器与生成器

更新时间:2026-07-12

在处理大量数据或需要按需生成序列时,迭代器和生成器是 Python 中最常用、最优雅的工具。它们让你能用统一的方式遍历任意对象,同时大幅节省内存。

迭代器协议与可迭代对象

Python 中的迭代基于一个简单的协议:

  • 可迭代对象:实现了 iter 方法,该方法返回一个迭代器对象。常见的可迭代对象包括 listtupledictsetstr、文件对象等。
  • 迭代器对象:实现了 iter 方法(通常返回自身)和 next 方法。next 方法在每次调用时返回下一个元素,当没有元素时抛出 StopIteration 异常。

可以用 iter() 函数获取迭代器,用 next() 函数获取下一个元素:

nums = [1, 2, 3]
it = iter(nums)
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
# next(it) 将引发 StopIteration

for 循环就是基于这个协议的语法糖:

for item in nums:
    print(item)

等价于:

it = iter(nums)
while True:
    try:
        item = next(it)
    except StopIteration:
        break
    print(item)

迭代器的特点

  • 惰性计算:只在需要时才生成下一个值,不像列表那样一次性把所有数据加载到内存。
  • 一次性使用:迭代器只能向前遍历,走完一次后便耗尽,再次使用需重新创建。
  • 节省内存:对于海量数据或无限序列,迭代器不会导致内存爆炸。

你可以为自定义类实现迭代器协议,使其支持 for 循环:

class CountDown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        num = self.current
        self.current -= 1
        return num

for n in CountDown(3):
    print(n)  # 3, 2, 1

生成器:最简单的迭代器

手动编写 iternext 方法还是略显繁琐。生成器提供了一种更简洁的创建迭代器的方式:直接编写一个包含 yield 关键字的函数。

  • 生成器函数:调用时会返回一个生成器对象(一个特殊的迭代器),并不立即执行函数体。每次调用 next() 或迭代时,函数会执行到 yield 语句,返回一个值并暂定执行,下一次调用再从暂停处继续。
  • 使用 yield 取代 return,且可以出现多次。
def count_down(n):
    while n > 0:
        yield n
        n -= 1

for num in count_down(3):
    print(num)   # 3, 2, 1

生成器函数的核心优势:

  • 自动保存状态:函数局部变量、执行位置在 yield 时自动保存,恢复时完全还原,无需手动维护状态。
  • 惰性求值:按需生成值,示例中大范围循环 while n > 0 并不会一次性计算出所有值并存入内存。
  • 代码简洁:相比手动实现迭代器类,生成器函数通常只要几行。

生成器表达式

生成器表达式可看作列表推导式的惰性版本,语法是将方括号改为圆括号:

squares = (x*x for x in range(1000000))
# squares 是一个生成器对象,不会立即创建 100 万个平方值
print(next(squares))  # 0
print(next(squares))  # 1
# 常直接用在迭代或汇总函数中
total = sum(x*x for x in range(1000000))

生成器表达式适用于需要临时产生序列,又不想浪费内存的场合,尤其在数据流水线中与 sum(), min(), max(), any(), all() 等内建函数配合极为方便。

实用场景:处理大文件

逐行读取大文件正是迭代器的典型应用。文件对象本身是可迭代的,它一行一行地产生数据,不会一次性将整个文件读入内存:

with open('large_file.txt') as f:
    for line in f:          # 每次只读取一行
        process(line)

若你需要对每一行进行过滤或转换,可以在中间加入生成器函数或生成器表达式,构建出一条数据处理流水线:

def non_empty_lines(lines):
    for line in lines:
        if line.strip():
            yield line.strip()

with open('data.txt') as f:
    clean_lines = non_empty_lines(f)
    for line in clean_lines:
        print(line)

进阶:生成器与协程

生成器不仅可以产出值(yield),还能接收值,甚至同时收发,这使其可以演化成协程。在 Python 3.5 引入 async/await 之前,协程就是基于增强的生成器实现的。例如使用 yield 表达式接收来自外部的值:

def echo():
    while True:
        received = yield
        print(f"收到: {received}")

e = echo()
next(e)           # 启动生成器,执行到 yield 暂停
e.send("Hello")   # 发送值,输出“收到: Hello”
e.send("World")   # 输出“收到: World”

不过,在现代 Python 中,真正的异步编程已主要使用 asyncioasync/await,生成器协程模式主要用于底层框架或兼容老代码。生成器最常用的身份仍然是最简便、最高效的内存友好型迭代器。

小结

  • 任何支持 for 循环的对象都是可迭代对象,背后是迭代器协议。
  • 生成器是创建迭代器的快捷方式,用 yield 可轻松实现惰性求值。
  • 处理大数据流或不适合一次性加载的数据时,优先考虑生成器,既能节省内存,又能保持代码清晰。