从零食到大数据:用生活场景理解 Python 迭代器与生成器

· 约 4 分钟 ·
本文目录(16 节)

前言

Python 的迭代器和生成器常被拿来面试考,也常出现在高效处理大数据的场景。可一提到这些概念,很多同学脑袋就大了——今天咱们换个思路,用超市买零食的生活场景,把这些看似“高级”的知识点讲清楚!


1. 用零食理解核心概念

  • 一次性全搬走(如把整箱零食一口气搬回家):像列表、字典等可迭代对象,所有数据一次性都加载到内存里,占地方。
  • 吃完再拿下一包(每次只取一包,吃完再拿):就像迭代器和生成器的“懒加载”,用多少取多少,省力又省空间!

一句话总结:

  • 迭代器:自己造了个“取零食”的机器,要手动实现“怎么取”。
  • 生成器:用“yield”关键字,像买了现成的自动取零食机,更省心。

2. 迭代器(Iterator)

2.1 概念与特点

  • 惰性取值:只在需要的时候才取出一个元素,不提前加载所有内容。

  • 实现协议:

    • __iter__():返回自身。
    • __next__():返回下一个元素,没有则抛出StopIteration异常。

2.2 创建迭代器实例

假如你有 4 包饼干,想每次只拿一包:

snacks = ["奥利奥", "薯片", "巧克力棒", "饼干"]
snack_iter = iter(snacks)
print(next(snack_iter))   # 奥利奥
print(next(snack_iter))   # 薯片
print(next(snack_iter))   # 巧克力棒
print(next(snack_iter))   # 饼干

2.3 自定义迭代器

自己做一个“抽奖箱”迭代器,每次抽出一个奖品:

class LuckyBox:
    def __init__(self, gifts):
        self.gifts = gifts
        self.index = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.index < len(self.gifts):
            gift = self.gifts[self.index]
            self.index += 1
            return gift
        else:
            raise StopIteration

lucky_iter = LuckyBox(["玩偶", "水杯", "U盘"])
for gift in lucky_iter:
    print("抽到了:", gift)

3. 可迭代对象 vs 迭代器

  • 可迭代对象(如列表、字符串、range)只实现了__iter__(),不能直接用next()。
  • 迭代器同时实现了__iter__()和__next__(),能直接用next()取数据。

快速判断方法:

my_list = ["薯片", "可乐"]
print(hasattr(my_list, "__iter__"))   # True
print(hasattr(my_list, "__next__"))   # False

my_iter = iter(my_list)
print(hasattr(my_iter, "__iter__"))   # True
print(hasattr(my_iter, "__next__"))   # True
print(next(my_iter))                  # 薯片

4. 生成器(Generator)

4.1 概念与特点

  • 用 yield 编写:省去了手动管理状态。
  • 支持惰性计算:每次 next() 生成一个数据,随用随取。

4.2 生成器函数实例

模拟“分批领取快递”,每次通知取一件:

def express_pickup():
    print("通知领取快递:第1件")
    yield "快递1"
    print("通知领取快递:第2件")
    yield "快递2"
    print("通知领取快递:第3件")
    yield "快递3"

pickup = express_pickup()
print(next(pickup))   # 通知领取快递:第1件 快递1
print(next(pickup))   # 通知领取快递:第2件 快递2
print(next(pickup))   # 通知领取快递:第3件 快递3
# print(next(pickup))   # StopIteration(没有快递了)

4.3 生成器表达式

用小括号生成懒加载数据(如:自动生成桌号)

table_numbers = (f"桌号-{i}" for i in range(1, 6))
print(next(table_numbers))  # 桌号-1
print(next(table_numbers))  # 桌号-2
for table in table_numbers:
    print(table)  # 桌号-3,桌号-4,桌号-5

4.4 大数据应用举例

模拟一行一行处理超大日志文件:

def read_log(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            yield line.strip()

# for log in read_log("mylog.txt"):
#     print(log)

5. 练习:生成器版斐波那契数列

取前10个“成长值”:

def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield b
        a, b = b, a + b

for num in fibonacci(10):
    print(num, end=" ")
# 输出:1 1 2 3 5 8 13 21 34 55

6. 迭代器 vs 生成器 总结对比

特性迭代器生成器
实现方式类定义,手动实现方法函数 + yield,自动实现
代码简洁性代码较复杂非常简单
状态管理需手动维护索引等状态自动保存函数执行位置
惰性计算支持支持
适合场景复杂遍历、定制迭代逻辑简单数据流、一次性生成流程

7. 必知细节与易错点

同一个生成器实例通常只消费一次。若日志预览先把它遍历完,再交给统计函数,后者可能收到空数据。需要重复读取时,应重新创建生成器,或在确认内存可接受后物化;这不是给对象再套一个 iter() 就能重置的。

  1. 迭代器和生成器都只能往前,不能回退

    被 next() 或 for 循环取过的元素,无法回头再取,生成器只能遍历一次。

    nums = iter([10, 20, 30])
    for n in nums:
        print(n)  # 10 20 30
    for n in nums:
        print(n)  # 什么都不输出(已经耗尽)
  2. 生成器可以动态接收外部值(send)

    send() 能给生成器传递数据,实现协程效果。

    def echo():
        received = yield "hello"
        yield f"got:{received}"
    
    g = echo()
    print(next(g))         # hello
    print(g.send("test"))  # got:test
  3. 判断对象是迭代器还是可迭代对象

    推荐用 collections.abc 判断:

    from collections.abc import Iterator, Iterable
    print(isinstance([], Iterator))         # False
    print(isinstance([], Iterable))         # True
    print(isinstance(iter([]), Iterator))   # True
  4. 常见支持迭代协议的数据类型

    • 所有内置集合类型(list、tuple、set、dict、str)
    • 文件对象(for line in open("file.txt") 本质就是迭代器)
    • 自定义实现了 __iter__ 或 __next__ 的类
  5. for 循环的本质

    for x in obj: 实际做的是:先 iter(obj),然后每次 next(),直到 StopIteration。

  6. 常见易错点与坑

    • next() 记得处理 StopIteration,for 循环最安全。
    • 生成器表达式只能用一次,不能回头。多个变量会“用光”生成器:
    g = (x for x in range(3))
    a = list(g)
    b = list(g)
    print(a, b)  # [0, 1, 2] []
  7. 生成器也能 return(用来抛 StopIteration)

    在生成器里 return,会触发 StopIteration,并可带返回值(少用,偶有面试考)。

    def foo():
        yield 1
        return 100
    g = foo()
    try:
        print(next(g))
        print(next(g))
    except StopIteration as e:
        print("生成器return的值:", e.value)  # 生成器return的值:100
  8. itertools 标准库

    itertools 提供强大的“流式数据处理”工具,推荐配合生成器、迭代器处理大数据、组合、分组、切片等。

    import itertools
    for x in itertools.islice(range(100), 10, 20):
        print(x)  # 输出10~19
  9. Python 3.x 推荐用法

    • dict.keys()、dict.values()、dict.items() 返回可迭代视图对象(不是 list,不是迭代器),可以 for 遍历,也能 iter()。
    • 注意和 Python 2.x 的区别。
  10. 实际开发建议

    • 处理大数据优先用生成器、迭代器,节省内存。
    • 只需遍历一次用生成器;需多次遍历、查找,转 list。
    • 组合式写法可读性优先,别滥用一行流。

8. 一句话总结

迭代器和生成器的精髓就是“懒加载”——数据不用一次性拿完,只在需要时才生成,用于大数据、流式处理、节省内存都非常高效。

建议: 需要简单的数据流,用生成器;遇到更复杂或需要自定义取数规则,再手写迭代器!


觉得有用欢迎点赞收藏,关注 @Zack-Zhang1031 持续更新!

💬 评论

这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。

正在连接评论服务,首次访问可能需要几秒…