广告
您当前的位置: 首页 >  技术 >  Python

Python 中的生成器 (Generators) 与迭代器 (Iterators) 深度剖析及自定义迭代协议

作者:管理员 时间:2026-06-18 阅读数:3人阅读

Python 中的生成器 (Generators) 与迭代器 (Iterators) 深度剖析及自定义迭代协议

在 Python 中,**迭代(Iteration)**是我们处理数据时最频繁的操作。无论是遍历列表、读取文件还是过滤字典,我们都习惯于使用 for...in 循环。然而,你是否曾想过:for 循环在底层是如何一步步遍历对象的?为什么某些大型数据集(如数 G 的日志文件)用普通列表处理会爆内存,而使用生成器却能只占用几 KB 的内存?

这一切的答案都在 Python 的**迭代器协议(Iterator Protocol)**与**生成器(Generators)**中。理解并掌握这两个核心概念,是编写高效、内存友好代码的分水岭。

本文将带你由浅入深剖析迭代器与生成器的底层机制,并展示如何通过自定义迭代协议来优化程序性能。


一、 核心概念:可迭代对象 (Iterable) vs 迭代器 (Iterator)

要理解迭代机制,我们必须厘清这两个极其容易混淆的概念:

1. 可迭代对象 (Iterable)

一个对象如果能被 for 循环遍历,它就是**可迭代对象**。例如:列表、元组、字符串、字典等。 在底层,可迭代对象必须实现 __iter__() 方法,该方法负责返回一个**迭代器对象**。

2. 迭代器 (Iterator)

迭代器是负责在迭代过程中“吐出”数据的具体工具。迭代器必须实现两个方法,即**迭代器协议**:

  • __iter__():返回迭代器自身。
  • __next__():每次被调用时,返回容器的下一个元素。如果所有元素已被吐完,则抛出 StopIteration 异常来告知迭代结束。

3. for 循环的底层工作机制

当我们在 Python 中执行 for item in my_list: 时,解释器在后台实际上执行了以下工作:

my_list = [1, 2, 3]

# 1. 获取迭代器对象
it = iter(my_list)  # 相当于调用 my_list.__iter__()

# 2. 循环获取下一个元素,直到遇到 StopIteration
while True:
    try:
        item = next(it)  # 相当于调用 it.__next__()
        # 执行 for 循环体内的代码
        print(item)
    except StopIteration:
        # 捕获退出异常,安全结束循环
        break

二、 自定义迭代协议实战

既然了解了迭代器协议,我们就可以通过自定义类来实现一个斐波那契数列(Fibonacci)的迭代器,从而避免一次性在内存中生成庞大的列表:

class Fibonacci:
    def __init__(self, limit):
        self.limit = limit  # 控制生成的最大个数
        self.count = 0
        self.a = 0
        self.b = 1

    def __iter__(self):
        # 迭代器协议要求:返回迭代器自身
        return self

    def __next__(self):
        if self.count >= self.limit:
            # 达到上限,抛出异常终止迭代
            raise StopIteration
        
        result = self.a
        self.a, self.b = self.b, self.a + self.b
        self.count += 1
        return result

# 像使用普通列表一样遍历自定义迭代器
fib = Fibonacci(10)
for num in fib:
    print(num, end=" ")  # 输出: 0 1 1 2 3 5 8 13 21 34

三、 核心机制:生成器 (Generators)

编写自定义迭代器类虽然强大,但需要定义类属性和维护状态,显得有些繁琐。为了简化迭代器的编写,Python 引入了**生成器(Generators)**。

1. 什么是生成器?

生成器是一种特殊的函数,它不使用 return 返回结果,而是使用 **yield** 关键字。 当一个函数包含 yield 时,它在调用时**不会执行函数体**,而是直接返回一个**生成器对象**(生成器对象自动实现了迭代器协议)。

def simple_generator():
    print("-> 第一次运行")
    yield 1
    print("-> 第二次运行")
    yield 2

gen = simple_generator()
print(next(gen))  # 输出: -> 第一次运行 
 1
print(next(gen))  # 输出: -> 第二次运行 
 2

每次调用 next(gen) 时,函数会执行到 yield 处,返回其后面的值并**挂起(暂停)**当前状态;当下一次调用 next() 时,函数会从刚才挂起的位置继续向下执行。

2. 延迟计算 (Lazy Evaluation) 与内存优化

生成器的精髓在于**延迟计算**:它只有在被请求(调用 next())时才计算并返回下一个值,而不是一次性把所有数据都计算好存放在内存里。

我们可以做一个直观的内存对比:

import sys

# 使用列表推导式:一次性在内存中生成 100 万个整数
list_comp = [i for i in range(1000000)]
print(f"列表推导式占用内存: {sys.getsizeof(list_comp)} 字节")  # 约 8MB

# 使用生成器表达式:只保存生成逻辑,不保存元素本身
gen_expr = (i for i in range(1000000))
print(f"生成器表达式占用内存: {sys.getsizeof(gen_expr)} 字节")   # 仅 112 字节!

在处理数百万行的大型日志文件时,使用生成器读取文件,能够确保无论文件有多大,内存占用始终保持在几 KB 的级别:

def read_large_file(file_path):
    with open(file_path, 'r') as f:
        for line in f:
            yield line  # 每次仅读取并返回一行,处理完毕后释放,极度省内存

四、 最佳实践与避坑指南

  1. **迭代器只能被消费一次**:迭代器就像一条单向流,一旦遍历完毕(抛出了 StopIteration),它就空了。如果需要再次遍历,必须重新创建迭代器。
  2. **在生成器中合理使用 `return`**:在 Python 3 中,生成器内部可以使用 return value。这并不会返回数据给 yield 接收者,而是会作为 StopIteration(value) 异常的值抛出。
  3. **利用 `contextlib` 配合生成器**:上一节讲到的 `@contextmanager` 上下文管理器,正是利用生成器挂起与恢复执行的特性,配合 yield 来划分资源开启与关闭边界的。

五、 总结

可迭代对象、迭代器和生成器是 Python 处理集合数据的底层大基石。通过合理运用它们,我们能够实现:

  • **时间上的解耦**:需要数据时才去计算或读取。
  • **空间上的极致优化**:以微不足道的内存开销处理海量乃至无限的数据流。

编写 Python 程序时,当遇到大型循环或大型数据源读写,优先考虑使用生成器和迭代器,是迈向 Python 高级开发者的必经之路。

本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。

评论交流 (0)

正在加载评论...
头像

杨青青

当你还撑不起你的梦想时,就要去奋斗。如果缘分安排我们相遇,请不要让她擦肩和过。我们一起奋斗!

微信