Python 迭代器与生成器

标签: #Python #迭代器 #生成器 #yield #延迟计算
学习周期:1 天 | 核心目标:理解迭代协议,掌握生成器实现延迟计算,对比列表推导式与生成器表达式的内存差异


3.2 迭代器与生成器

迭代器与生成器是 Python 中高效处理序列数据的核心工具,核心优势是 延迟计算(惰性求值) —— 不一次性生成所有数据,而是按需生成,大幅节省内存(尤其适合处理海量数据)。


3.2.1 可迭代对象(__iter__)与迭代器(__next__

核心概念对比
类型 核心特征 关键方法 示例
可迭代对象 能被 for 循环遍历,可生成迭代器 __iter__():返回迭代器 liststrtupledictset
迭代器 可逐个返回元素,可记录遍历位置,只能遍历一次 __next__():返回下一个元素;__iter__():返回自身 iter(list)、文件对象、生成器对象
判断可迭代对象和迭代器
from collections.abc import Iterable, Iterator

lst = [1, 2, 3]
print(isinstance(lst, Iterable))   # True(可迭代)
print(isinstance(lst, Iterator))   # False(不是迭代器)

it = iter(lst)                     # 获取迭代器
print(isinstance(it, Iterator))    # True
手动使用迭代器
lst = [1, 2, 3]
it = iter(lst)          # 获取迭代器
print(next(it))         # 1
print(next(it))         # 2
print(next(it))         # 3
# print(next(it))       # StopIteration 异常
for 循环的工作原理
# for 循环等价于:
it = iter(可迭代对象)
while True:
    try:
        value = next(it)
        # 执行循环体
    except StopIteration:
        break
自定义迭代器
class MyIterator:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current > self.end:
            raise StopIteration
        temp = self.current
        self.current += 1
        return temp

my_iter = MyIterator(1, 3)
for num in my_iter:
    print(num)   # 1, 2, 3
# 再次遍历(迭代器已耗尽,无输出)
for num in my_iter:
    print(num)
易错点说明
  • 可迭代对象 ≠ 迭代器:可迭代对象需要通过 iter() 转换为迭代器才能手动遍历。
  • 迭代器只能遍历一次:遍历结束后指针停在最后,无法重置,需重新创建迭代器。
  • for 循环的底层逻辑:先调用 iter(可迭代对象) 生成迭代器,再循环调用 next(),直到捕获 StopIteration 异常,自动终止循环。

3.2.2 生成器函数(yield)—— 延迟计算,节省内存

生成器是一种特殊的迭代器,使用 yield 关键字定义。生成器函数在每次调用 next() 时执行到 yield 暂停并返回一个值,下次调用从暂停处继续。

核心特性
  • 延迟计算:只在需要时生成值,节省内存。
  • 状态保持:函数内的局部变量在每次 yield 之间保持。
  • 无限序列:可以表示无限大的序列(如斐波那契数列)。
基本用法
def my_generator(start, end):
    current = start
    while current <= end:
        yield current
        current += 1

gen = my_generator(1, 3)          # 创建生成器对象,函数体不会立即执行
print(type(gen))                  # <class 'generator'>
print(isinstance(gen, Iterator))  # True

# 手动遍历
print(next(gen))   # 1
print(next(gen))   # 2
print(next(gen))   # 3
# print(next(gen)) # StopIteration

# for 循环遍历(推荐)
for num in my_generator(1, 3):
    print(num)   # 1, 2, 3
生成器实现斐波那契数列(节省内存)
def fibonacci(limit):
    a, b = 0, 1
    while a < limit:
        yield a
        a, b = b, a + b

for num in fibonacci(100):
    print(num, end=" ")   # 0 1 1 2 3 5 8 13 21 34 55 89
生成器的进阶方法(sendclose
def echo():
    while True:
        received = yield
        print(f"收到:{received}")

gen = echo()
next(gen)                # 预激生成器,执行到第一个 yield
gen.send("Hello")        # 收到:Hello
gen.send("World")        # 收到:World
gen.close()              # 关闭生成器
应用场景
  • 处理海量数据(如读取超大文件、生成百万级随机数),避免内存溢出。
  • 惰性计算场景:批量处理数据,不需要一次性获取所有结果。
  • 替代列表推导式:当数据量较大时,用生成器替代列表推导式。

3.2.3 生成器表达式

生成器表达式类似于列表推导式,但使用圆括号 () 而不是方括号 []。它返回一个生成器对象,而不是一次性创建列表,适合处理大量数据。

语法
(expression for item in iterable if condition)
示例与对比
# 列表推导式:立即生成所有值,占用内存
squares_list = [x**2 for x in range(10)]
print(squares_list)      # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 生成器表达式:延迟计算,不占内存
squares_gen = (x**2 for x in range(10))
print(squares_gen)       # <generator object <genexpr> at 0x...>

for val in squares_gen:
    print(val, end=" ")  # 0 1 4 9 16 25 36 49 64 81

# 一次性转换为列表(失去生成器的优势)
squares_list2 = list(x**2 for x in range(10))
内存对比
import sys

# 列表推导式:立即分配内存
list_comp = [x for x in range(1000000)]
print(sys.getsizeof(list_comp))   # 约 8 MB

# 生成器表达式:几乎不占内存
gen_exp = (x for x in range(1000000))
print(sys.getsizeof(gen_exp))     # 约 104 字节
与高阶函数配合
# 计算 1 到 1e6 的平方和(无需创建巨大列表)
total = sum(x**2 for x in range(1, 1000001))
print(total)

# 找出 1 到 1e6 中能被 7 整除的最大数
max_val = max(x for x in range(1, 1000001) if x % 7 == 0)
print(max_val)
实战场景:逐行读取大文件
def read_large_file(file_path):
    return (line.strip() for line in open(file_path, "r", encoding="utf-8"))

# 使用:按需读取,内存占用极低
for line in read_large_file("large_file.txt"):
    print(line)   # 逐行处理
易错点说明
  • 生成器表达式的圆括号不能省略,否则会变成普通表达式。
  • 生成器表达式也是“一次性的”,遍历结束后无法重复使用,需重新定义。
  • 数据量小且需要多次访问时用列表推导式;数据量大且只需遍历一次时用生成器表达式。

3.2.4 迭代器、生成器与列表推导式的选择

场景 推荐方案 原因
数据量小,需要多次遍历 列表推导式 值已存储在内存中,访问快
数据量大,只需遍历一次 生成器表达式或生成器函数 节省内存,延迟计算
需要复杂逻辑控制 生成器函数(yield 可暂停、保持状态,代码清晰
需要向生成器发送数据 生成器函数 + send() 双向通信

📚 学习资料(Obsidian 可直接收藏)


🎯 学习建议(1 天计划)

  1. 上午:理解可迭代对象与迭代器的区别,手动实现 __iter____next__,练习生成器函数(yield)。
  2. 下午:掌握生成器表达式,对比列表推导式的内存占用,完成实战练习(大文件读取、无限序列生成)。

✅ 核心要点总结

  1. 可迭代对象:实现了 __iter__(),返回迭代器。常见的有 liststrdict 等。
  2. 迭代器:实现了 __iter__()__next__(),只能遍历一次。for 循环依赖迭代协议。
  3. 生成器函数:使用 yield 的函数,调用时返回生成器对象(属于迭代器),支持延迟计算和状态保持。
  4. 生成器表达式:类似列表推导式但使用 (),返回生成器对象,适合大数据量处理。
  5. 内存优势:生成器和生成器表达式不一次性存储所有元素,处理大数据集时能显著降低内存占用。
  6. 一次性特点:生成器和迭代器都只能遍历一次,无法重置。

练习题(自测)

  1. 定义一个可迭代对象 Fibonacci,使用 __iter____next__ 实现斐波那契数列的前 n 项。
  2. 编写生成器函数 read_large_file(file_path),逐行读取大文件(模拟),每次 yield 一行。
  3. 使用生成器表达式找出 1 到 100000 中所有能被 13 整除的数,并计算它们的和(不使用列表)。
  4. 实现一个无限生成器 cycle(iterable),无限循环返回可迭代对象中的元素(如 cycle('AB') 输出 A B A B ...)。
  5. 写一个生成器 primes(),无限产生素数(使用埃拉托斯特尼筛法或试除法)。

建议在 Jupyter Notebook 或本地环境中运行,比较生成器和列表推导式的内存差异。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐