Python3 迭代器与生成器(建议收藏)

Python3 迭代器与生成器:从基础到实战的完整解析

在 Python 编程中,Python3 迭代器与生成器 是两个非常核心且强大的概念。它们不仅提升了代码的可读性和性能,还在处理大数据集、流式处理、内存优化等场景中扮演着关键角色。如果你已经熟悉了列表、循环和函数,那么掌握这两个特性,就等于打开了通往高效编程的大门。

本文将从最基础的概念讲起,通过实际代码示例,逐步带你理解 Python3 迭代器与生成器 的工作原理与应用场景。无论你是初学者,还是已有一定经验的开发者,都能从中获得实用的启发。


迭代器的起源:为什么需要迭代?

在 Python 中,我们经常使用 for 循环来遍历一个序列,比如列表、元组、字符串等。例如:

fruits = ["apple", "banana", "cherry"]

for fruit in fruits:
    print(fruit)

这段代码看起来简单,但背后其实有一个关键机制在支撑——迭代器

简单来说,迭代器是一种“可以逐个访问元素”的对象。它不直接存储所有数据,而是按需提供下一个元素。就像你在听音乐时,播放器不会把整张专辑都加载到内存里,而是每次只播放当前一首歌。这种“按需加载”的思想,就是迭代器的核心。

Python 中的 for 循环本质上就是通过调用 iter() 函数获取一个迭代器,再不断调用 next() 方法来获取下一个元素。


什么是迭代器?一个可迭代对象的“访问器”

在 Python 中,任何实现了 __iter__()__next__() 方法的对象,都可以被称为迭代器。

我们来手动实现一个简单的迭代器,来感受它的运作机制:

class CountDown:
    def __init__(self, start):
        self.start = start  # 记录起始值

    def __iter__(self):
        # 返回自身,因为这个类本身实现了 __next__ 方法
        return self

    def __next__(self):
        if self.start <= 0:
            raise StopIteration  # 当没有更多元素时,抛出异常终止迭代
        self.start -= 1
        return self.start + 1  # 返回当前值(注意:先减后返回,所以要加1)

countdown = CountDown(5)

for num in countdown:
    print(num)

代码注释说明:

  • __iter__():返回迭代器本身,让 for 循环可以获取它。
  • __next__():每次调用返回下一个值。当没有值可返回时,必须抛出 StopIteration 异常,否则 for 循环会无限运行。
  • StopIteration 是 Python 的内置异常,用于通知迭代结束。

⚠️ 注意:一旦迭代器被耗尽(即 next() 被调用到尽头),它就不能重新开始。如果需要再次使用,必须重新创建实例。


生成器:更优雅的迭代器创建方式

虽然手动实现迭代器很清晰,但写起来太繁琐。Python 提供了更简洁的方式来创建迭代器——生成器(Generator)

生成器是通过函数定义的,但使用 yield 关键字来“暂停”并返回值。当再次调用时,它从上次暂停的地方继续执行。

生成器函数的基本语法

def fibonacci_generator(n):
    a, b = 0, 1
    count = 0
    while count < n:
        yield a  # 暂停并返回 a 的值
        a, b = b, a + b  # 更新斐波那契数列的下一对值
        count += 1

fib = fibonacci_generator(10)

for num in fib:
    print(num, end=" ")

输出:

0 1 1 2 3 5 8 13 21 34

代码注释说明:

  • yield 不是 return,它不会结束函数,而是将控制权交还给调用者,并记住当前状态。
  • 下次调用 next()(或在 for 循环中)时,函数从 yield 语句之后继续执行。
  • 生成器不会一次性生成所有值,而是按需计算,极大节省内存。

生成器表达式:轻量级的生成器

除了生成器函数,Python 还支持生成器表达式,语法类似于列表推导式,但用圆括号 () 包裹。

squares_list = [x**2 for x in range(10)]

squares_gen = (x**2 for x in range(10))

print(type(squares_list))  # <class 'list'>
print(type(squares_gen))   # <class 'generator'>

for square in squares_gen:
    print(square, end=" ")

输出:

0 1 4 9 16 25 36 49 64 81

✅ 生成器表达式比列表推导式更节省内存,特别适合处理大文件或无限序列。


实际应用场景:处理大文件与流式数据

想象你有一个包含数百万行日志的文件,如果用传统方式读取并存入列表,内存会爆掉。

但用生成器,可以逐行读取,边读边处理:

def read_large_file(filename):
    with open(filename, "r", encoding="utf-8") as file:
        for line in file:
            yield line.strip()  # 每次返回一行,不加载全部

log_generator = read_large_file("large_log.txt")

for line in log_generator:
    if "ERROR" in line:
        print(line)

优势:

  • 内存占用始终稳定,不会随文件大小增长。
  • 可以处理比内存还大的数据。
  • 代码简洁,逻辑清晰。

迭代器与生成器的性能对比

为了直观感受两者的差异,我们来做一个简单的性能测试:

import time

def get_squares_list(n):
    return [i**2 for i in range(n)]

def get_squares_gen(n):
    for i in range(n):
        yield i**2

n = 1000000

start = time.time()
squares_list = get_squares_list(n)
list_time = time.time() - start

start = time.time()
squares_gen = get_squares_gen(n)
gen_time = time.time() - start

print(f"列表方式耗时: {list_time:.4f} 秒")
print(f"生成器方式耗时: {gen_time:.4f} 秒")

print(f"生成器的内存占用远低于列表,特别适合大数据场景")

结论:

  • 列表方式虽然速度快,但占用内存巨大。
  • 生成器方式初始耗时极小,后续按需生成,适合流式处理。

常见误区与最佳实践

误区 正确做法
认为生成器可以重复使用 生成器只能遍历一次,用完即“耗尽”
在生成器中使用 return 返回值 return 会抛出 StopIteration,建议用 yield
误将生成器表达式用于需要重复访问的场景 若需多次遍历,应转为列表或使用缓存

推荐最佳实践:

  • 处理大量数据时,优先使用生成器。
  • 需要多次遍历时,可将生成器结果转为列表:list(generator)
  • 在函数中使用 yield,让逻辑更清晰、内存更友好。

总结:掌握 Python3 迭代器与生成器 的意义

Python3 迭代器与生成器 不仅仅是语法糖,更是 Python 设计哲学的体现:优雅、高效、内存友好

通过理解它们的底层机制,你能写出更专业的代码,避免内存溢出,提升程序性能。尤其是在处理日志、数据流、大文件、API 接口返回等场景中,生成器几乎成了标准做法。

从今天起,当你写循环时,不妨多问一句:“能不能用生成器优化?”——这会成为你编程路上的“隐形加速器”。

记住:好代码,不在于写了多少行,而在于它如何高效地“思考”

希望这篇文章能帮你真正理解并熟练运用 Python3 迭代器与生成器,在实战中游刃有余。