Python: 迭代器与生成器

你已经在用 for x in list: 遍历数据了,但有没有想过 in 后面可以跟哪些东西?range(1000000) 为什么不占内存?迭代器生成器就是答案。它们让你能处理"无限"的数据流——按需生成,不占内存。


1. 什么是迭代器协议

Python 中,任何可以被 for 循环遍历的对象都可迭代(iterable)。它们背后实现了迭代器协议

▶ 示例:迭代器协议基础

PYTHON
# 所有容器都是可迭代的
print(hasattr([1, 2, 3], "__iter__"))     # True
print(hasattr("abc", "__iter__"))          # True
print(hasattr(100, "__iter__"))            # False——数字不可迭代

# 手动执行迭代过程
numbers = [1, 2, 3]
iterator = iter(numbers)     # 获取迭代器

print(next(iterator))        # 1
print(next(iterator))        # 2
print(next(iterator))        # 3
# print(next(iterator))      # StopIteration —— 迭代结束
▶ 试一试

输出:

TEXT 📖 仅展示
# 执行成功

for 循环本质上就是:

▶ 示例:手动模拟 for 循环

PYTHON
numbers = [1, 2, 3]
it = iter(numbers)
while True:
    try:
        value = next(it)
        print(value)
    except StopIteration:
        break
▶ 试一试

输出:

TEXT 📖 仅展示
# 执行成功


2. 自定义迭代器

实现 __iter____next__ 方法,让你的类能用 for 遍历:

▶ 示例:倒计时迭代器

PYTHON
class Countdown:
    """倒计时迭代器"""
    def __init__(self, start):
        self.current = start
    
    def __iter__(self):
        return self                     # 迭代器返回自身
    
    def __next__(self):
        if self.current <= 0:
            raise StopIteration         # 停止迭代
        value = self.current
        self.current -= 1
        return value

# 使用
for i in Countdown(5):
    print(i, end=" ")                   # 5 4 3 2 1
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

▶ 示例:斐波那契迭代器(难度⭐⭐)

PYTHON
class Fibonacci:
    """斐波那契数列迭代器——生成前 N 个"""
    def __init__(self, count):
        self.count = count
        self.index = 0
        self.a, self.b = 0, 1
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index >= self.count:
            raise StopIteration
        self.index += 1
        result = self.a
        self.a, self.b = self.b, self.a + self.b
        return result

for num in Fibonacci(10):
    print(num, end=" ")                 # 0 1 1 2 3 5 8 13 21 34
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功


3. 生成器:yield

写一个类实现 __iter____next__ 挺麻烦的。生成器函数yield 就能搞定同样的事:

▶ 示例:倒计时生成器

PYTHON
def countdown(start):
    """倒计时生成器"""
    while start > 0:
        yield start
        start -= 1

# 使用
for i in countdown(5):
    print(i, end=" ")                   # 5 4 3 2 1
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

yieldreturn 不同——return 结束函数,而 yield 暂停函数,记住当前状态,下次调用时从暂停处继续。

▶ 示例:斐波那契生成器

PYTHON
# 同样的斐波那契——用 yield 实现,比迭代器类简洁得多
def fibonacci(count):
    a, b = 0, 1
    for _ in range(count):
        yield a
        a, b = b, a + b

for num in fibonacci(10):
    print(num, end=" ")                 # 0 1 1 2 3 5 8 13 21 34
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功
💡 生成器 vs 迭代器类: 生成器函数(yield)通常比手写迭代器类简洁得多。绝大多数场景下,生成器是首选。只有需要维护复杂状态或需要额外方法时,才考虑手写迭代器类。



4. 生成器的懒加载特性

生成器最大的优势是——不一次性生成所有数据,按需生产:

▶ 示例:生成器懒加载

PYTHON
def generate_numbers():
    """模拟逐个生成数字"""
    print("生成 1")
    yield 1
    print("生成 2")
    yield 2
    print("生成 3")
    yield 3

gen = generate_numbers()
print("创建了生成器,但还没执行")

print(next(gen))    # 生成 1 \n 1
print(next(gen))    # 生成 2 \n 2
print(next(gen))    # 生成 3 \n 3
▶ 试一试

输出:

TEXT 📖 仅展示
生成 1
生成 2
生成 3
创建了生成器,但还没执行

看到执行过程了吧?每次 next() 才执行到下一个 yield,然后停住。这个特性在处理大数据时非常有用:

▶ 示例:逐行读取大文件

TEXT 📖 仅展示
# 处理大文件的"懒加载"方式
def read_large_file(filename):
    """逐行读取大文件,不一次性加载到内存"""
    with open(filename, "r", encoding="utf-8") as f:
        for line in f:
            yield line.strip()

# 用生成器处理——每次只处理一行
for line in read_large_file("huge_log.txt"):
    if "ERROR" in line:
        print(line)  # 处理速度只受磁盘读取速度限制


5. 生成器表达式

和列表推导式很像,但用圆括号——生成器表达式是惰性求值的:

▶ 示例:生成器表达式

PYTHON
# 列表推导式——一次性生成所有数据
squares_list = [x ** 2 for x in range(1000000)]
print(f"列表大小:{len(squares_list)}")           # 1000000(占内存)

# 生成器表达式——按需生成
squares_gen = (x ** 2 for x in range(1000000))
print(f"生成器:{squares_gen}")                    # generator object(几乎不占内存)

# 用法一样——for 遍历
for i, val in enumerate(squares_gen):
    if i >= 5:
        break
    print(val, end=" ")                           # 0 1 4 9 16
▶ 试一试

输出:

TEXT 📖 仅展示
# 执行成功
对比 列表推导式 [] 生成器表达式 ()
内存 所有数据在内存中 按需生成,几乎不占
速度 生成快,后续访问快 生成惰性,每次计算
可复用 可以反复遍历 只能遍历一次
适用场景 数据量小、需要反复使用 数据量大、只需遍历一次
💡 什么时候用生成器? ① 数据量大(几千条以上)。② 只需遍历一次。③ 需要流式处理(逐行读文件、实时数据流)。④ 想表达"无限序列"的概念。"



6. 常见应用场景


❓ 常见问题

Q 生成器和普通函数有什么区别?
A 普通函数用 return 一次性返回结果,函数结束。生成器用 yield 逐个产生值,每次暂停保存状态,下次从暂停处继续。生成器函数每次调用返回一个新的生成器对象。
Q 生成器只能遍历一次,想反复遍历怎么办?
Alist(gen) 把生成器转成列表。但如果数据量太大,转列表会耗尽内存。折中方案:用 itertools.tee() 复制生成器,或者重新调用生成器函数生成一个新的。
Q yieldreturn 可以共存吗?
A 可以——return 在生成器中相当于"停止迭代"并返回 StopIteration 的值。不过很少这么用。实践中如果你发现一个函数既要 yield 又要 return,最好想想设计是否合理。

📖 小节


📝 作业

  1. 基础题(难度⭐):用生成器写一个 even_numbers(max_n) 函数,生成从 0 到 max_n 之间的所有偶数。

  2. 进阶题(难度⭐⭐):用生成器写一个 repeat_list(items, times) 函数,把列表中的每个元素重复 times 次。例如 list(repeat_list([1, 2, 3], 2)) 返回 [1, 1, 2, 2, 3, 3]

  3. 挑战题(难度⭐⭐⭐):写一个生成器 deep_flatten(nested),可以展开任意深度的嵌套列表。例如 list(deep_flatten([1, [2, [3, 4]], 5])) 返回 [1, 2, 3, 4, 5]提示:用 for 遍历元素,如果元素是列表则递归调用 yield from

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏