第31课:Python|迭代器、生成器、yield底层原理与实战精讲

在这里插入图片描述


📖 开篇导读

在前面的课程中,我们经常使用for循环遍历列表、字符串、字典等容器。你是否想过,for循环底层是如何工作的?为什么有些对象可以被遍历,而有些不能?是否有一种方式,可以让我们按需生成数据,而不是一次性把所有数据都放在内存里?

这些问题都指向Python中的迭代器(Iterator)生成器(Generator)。迭代器是支持for循环的底层机制;生成器是一种特殊的迭代器,可以用更简洁的语法实现惰性求值,在大数据处理、流式处理中非常有用。

💡 工作场景

  • 读取超大文件(如几十GB的日志)时,不能一次读入内存,需要逐行读取,这就是迭代器的应用。
  • 生成斐波那契数列无限序列,不可能提前存储所有值,而生成器可以无限生成。
  • 数据管道中,将多个处理步骤串联起来,每个步骤使用生成器实现惰性计算,节省内存。

本课将深入讲解:

  • 迭代器协议(__iter____next__
  • 可迭代对象 vs 迭代器的区别
  • 生成器函数(yield)的原理与用法
  • 生成器表达式
  • 生成器的进阶方法(sendclosethrow
  • 实战:读取大文件、无限序列、管道处理

学完本课,你将掌握Python中高效处理数据流的核心技术。


🎯 学习目标

目标编号具体掌握内容对应面试/工作价值
1️⃣理解迭代器协议,能够自定义迭代器类深入理解for循环本质
2️⃣区分可迭代对象迭代器面试常问区别
3️⃣掌握生成器函数yield),能编写生成器处理大数据序列
4️⃣掌握生成器表达式,与列表推导式对比节省内存的写法
5️⃣了解生成器的惰性求值特性及内存优势优化程序性能
6️⃣掌握生成器的进阶方法sendclosethrow协程基础

🔥 面试考点:“yield的作用是什么?”“生成器和迭代器的区别?”“如何判断一个对象是否可迭代?”“rangelist的区别(迭代器角度)?”


📚 知识点理论精讲

一、迭代器协议

在Python中,可迭代对象(Iterable)是指实现了__iter__方法的对象,该方法返回一个迭代器(Iterator)。迭代器实现了__iter____next__方法,__next__方法返回下一个元素,并在没有元素时抛出StopIteration异常。

1.1 可迭代对象与迭代器的区别

概念特点示例
可迭代对象实现了__iter__,可以用于for循环list, tuple, str, dict, range
迭代器实现了__iter____next__,记录迭代位置iter(list)返回的迭代器对象
# 可迭代对象
lst = [1,2,3]
print(hasattr(lst, '__iter__'))   # True
print(hasattr(lst, '__next__'))   # False

# 获取迭代器
it = iter(lst)
print(hasattr(it, '__iter__'))    # True
print(hasattr(it, '__next__'))    # True

# 手动迭代
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
# print(next(it))  # StopIteration

1.2 for循环的本质

# for 循环等价于:
it = iter(iterable)
while True:
    try:
        value = next(it)
        # 循环体
    except StopIteration:
        break

1.3 自定义迭代器

实现一个迭代器类,需要实现__iter__(返回自身)和__next__

class MyRange:
    def __init__(self, start, end):
        self.start = start
        self.end = end
        self.current = start
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 1
        return value

for i in MyRange(0, 5):
    print(i)   # 0 1 2 3 4

二、生成器函数与yield

生成器是一种简化迭代器创建的方式。定义一个函数,使用yield而不是return,这个函数就是生成器函数。调用生成器函数会返回一个生成器对象(属于迭代器)。

2.1 基本用法

def count_down(n):
    while n > 0:
        yield n
        n -= 1

gen = count_down(3)
print(next(gen))  # 3
print(next(gen))  # 2
print(next(gen))  # 1
# print(next(gen))  # StopIteration

for i in count_down(5):
    print(i)   # 5 4 3 2 1

2.2 yield的原理

当函数执行到yield时,会返回一个值,并暂停函数的执行,保存当前状态(局部变量、指令指针等)。下次调用next()时,从暂停处继续执行。

🧠 类比return就像跳下公交车,再也回不来;yield就像到站停车,你可以下去办事,然后回来继续坐车。

2.3 生成器与普通函数的对比

特性普通函数生成器函数
返回值returnyield
执行方式一次执行完毕逐次执行,可暂停
内存占用可能很大很小,惰性生成
多次调用每次重新执行生成器对象有状态

2.4 生成器应用场景

  • 生成无限序列(如斐波那契数列、自然数)
  • 处理大文件逐行读取
  • 数据流式处理(管道)

三、生成器表达式

类似于列表推导式,但使用圆括号而不是方括号。返回一个生成器对象,惰性计算。

# 列表推导式(立即计算,占用内存)
squares_list = [x**2 for x in range(1000000)]  # 占用大量内存

# 生成器表达式(惰性计算,不占用内存)
squares_gen = (x**2 for x in range(1000000))   # 几乎不占内存

# 使用
for s in squares_gen:
    if s > 100:
        break
    print(s)

生成器表达式可以用在函数调用中,作为参数时括号可以省略:

sum(x**2 for x in range(10))  # 而不是 sum([x**2 for x in range(10)])

四、生成器的高级方法

生成器对象除了__next__()(等价于next()),还有send()throw()close()方法。

4.1 send(value)

send可以向生成器发送一个值,该值会成为yield表达式的返回值。send也会使生成器继续执行到下一个yield

def accumulator():
    total = 0
    while True:
        value = yield total
        if value is None:
            break
        total += value

acc = accumulator()
next(acc)          # 启动生成器,执行到第一个yield,返回0
print(acc.send(10)) # 发送10,total=10,yield返回10
print(acc.send(20)) # 发送20,total=30,yield返回30
acc.close()

注意:必须先调用next()send(None)启动生成器。

4.2 throw(type, value, traceback)

在生成器内部引发一个异常。

def gen():
    try:
        yield 1
        yield 2
    except ValueError:
        print("收到ValueError")
        yield 3

g = gen()
print(next(g))  # 1
g.throw(ValueError)  # 输出"收到ValueError",然后yield 3

4.3 close()

关闭生成器,在生成器内部引发GeneratorExit异常。生成器可以捕获该异常进行清理。

def gen():
    try:
        yield 1
    finally:
        print("cleanup")

g = gen()
print(next(g))
g.close()  # 输出"cleanup"

五、迭代器与生成器的性能优势

  • 节省内存:不需要一次性存储所有数据,而是按需生成。
  • 减少延迟:可以处理无限序列,每次只计算一个值。
  • 提高响应性:在流式处理中,可以边生成边处理。

对比:读取大文件,使用readlines()会一次加载所有行到内存;而直接迭代文件对象是逐行生成,内存友好。

# 坏方式:一次性读取所有行
with open('large.log') as f:
    lines = f.readlines()  # 如果文件有几GB,内存爆炸

# 好方式:逐行迭代(文件对象本身就是迭代器)
with open('large.log') as f:
    for line in f:
        process(line)

💻 代码案例实操

案例1:自定义迭代器——斐波那契数列

"""
fibonacci_iterator.py
实现一个迭代器版本的斐波那契数列
"""

class Fibonacci:
    def __init__(self, max_count):
        self.max_count = max_count
        self.count = 0
        self.a = 0
        self.b = 1
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.count >= self.max_count:
            raise StopIteration
        self.count += 1
        if self.count == 1:
            return self.a
        elif self.count == 2:
            return self.b
        else:
            self.a, self.b = self.b, self.a + self.b
            return self.b

fib = Fibonacci(10)
for num in fib:
    print(num, end=' ')  # 0 1 1 2 3 5 8 13 21 34

案例2:生成器实现无限自然数

"""
infinite_numbers.py
使用生成器产生无限的自然数序列
"""

def natural_numbers(start=0):
    n = start
    while True:
        yield n
        n += 1

# 取前10个自然数
nums = natural_numbers()
for _ in range(10):
    print(next(nums), end=' ')  # 0 1 2 3 4 5 6 7 8 9

# 使用 itertools.islice 截取有限个
import itertools
first_10 = list(itertools.islice(natural_numbers(), 10))
print(first_10)

案例3:生成器读取大文件(按行过滤)

"""
large_file_filter.py
逐行读取大文件,过滤包含特定关键字的行,返回生成器
"""

def filter_lines(filepath, keyword):
    """生成器函数:逐行读取文件,yield包含关键字的行"""
    with open(filepath, 'r', encoding='utf-8') as f:
        for line in f:
            if keyword in line:
                yield line.rstrip('\n')

# 模拟大文件
with open('test_large.txt', 'w') as f:
    for i in range(1000):
        f.write(f"line {i}: this is a test\n")

# 使用生成器,不会一次加载所有行
for line in filter_lines('test_large.txt', '500'):
    print(line)   # 只输出包含500的行

案例4:生成器管道处理数据流

"""
generator_pipeline.py
多个生成器串联形成数据处理管道
"""

def read_file(filepath):
    """读取文件,yield每一行"""
    with open(filepath, 'r', encoding='utf-8') as f:
        for line in f:
            yield line

def filter_lines(lines, keyword):
    """过滤包含关键词的行"""
    for line in lines:
        if keyword in line:
            yield line

def convert_to_upper(lines):
    """将行转为大写"""
    for line in lines:
        yield line.upper()

def take(lines, n):
    """取前n个元素"""
    for i, line in enumerate(lines):
        if i >= n:
            break
        yield line

# 构建管道:读取 -> 过滤 -> 转大写 -> 取前5
with open('test_large.txt', 'w') as f:
    for i in range(100):
        f.write(f"log: event {i}\n")

pipeline = take(
    convert_to_upper(
        filter_lines(
            read_file('test_large.txt'),
            'event 5'
        )
    ), 5
)

for item in pipeline:
    print(item, end='')

案例5:生成器实现惰性求值——素数生成

"""
prime_generator.py
生成无限素数序列
"""

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n**0.5)+1):
        if n % i == 0:
            return False
    return True

def prime_generator():
    """生成无限素数序列"""
    n = 2
    while True:
        if is_prime(n):
            yield n
        n += 1

# 获取前20个素数
import itertools
primes = prime_generator()
first_20 = list(itertools.islice(primes, 20))
print(first_20)
# 还可以继续使用原来的生成器获取后面的素数
next_10 = list(itertools.islice(primes, 10))
print(next_10)

案例6:yieldsend交互——可控制的累加器

"""
yield_send_demo.py
使用send方法向生成器发送数据,实现交互式累加
"""

def interactive_accumulator():
    total = 0
    while True:
        value = yield total   # 返回当前total,并接收外部send的值
        if value is None:
            break
        total += value
    return total   # 生成器结束后,StopIteration的value可以捕获

acc = interactive_accumulator()
# 启动生成器,必须先调用next或send(None)
next(acc)   # 相当于 acc.send(None),返回0
print(acc.send(10))  # 返回10
print(acc.send(20))  # 返回30
print(acc.send(5))   # 返回35
try:
    acc.send(None)   # 触发break,生成器结束,抛出StopIteration,返回值35
except StopIteration as e:
    print(f"最终累加和: {e.value}")

案例7:yield from语法糖

yield from可以将迭代器中的元素逐个yield出来,简化嵌套生成器。

"""
yield_from_demo.py
演示yield from的用法
"""

def sub_gen():
    yield 1
    yield 2
    yield 3

def main_gen():
    # 普通方式
    for x in sub_gen():
        yield x
    # 等价于 yield from sub_gen()

def chain(*iterables):
    """使用yield from连接多个可迭代对象"""
    for it in iterables:
        yield from it

# 使用
for x in chain([1,2], "ab", range(3)):
    print(x, end=' ')  # 1 2 a b 0 1 2

# 递归生成器(遍历嵌套列表)
def flatten(nested):
    """扁平化嵌套列表"""
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item

nested_list = [1, [2, [3, 4], 5], 6]
flat = list(flatten(nested_list))
print(flat)  # [1,2,3,4,5,6]

⚠️ 易错点避坑总结

序号坑点描述后果解决方案
1混淆可迭代对象和迭代器试图对非迭代器调用next()报错使用iter()获取迭代器
2生成器函数中既用return又用yieldreturn会触发StopIteration并返回仅在需要提前终止时使用return
3忘记启动生成器就直接sendTypeError: can't send non-None value to a just-started generator先调用next(g)g.send(None)
4生成器表达式与列表推导式混淆误以为生成器表达式也支持所有列表方法(如索引)生成器只能迭代一次,不支持索引
5多次迭代同一个生成器第二次迭代得到空结果需要重新创建生成器对象
6在迭代生成器时修改被迭代的容器可能引发异常或行为不确定不要在迭代过程中修改
7yield from后没有正确传播异常异常无法被生成器外部捕获yield from会自动处理(子生成器的异常会传给调用者)
8__next__中抛出StopIteration之前没有保存状态迭代器不可恢复正常实现即可
9生成器中无限循环没有终止条件会导致无限生成确保有退出条件或由调用方控制
10将生成器视为线程(试图实现异步)生成器只是协程基础,但不是操作系统线程使用asyncio或真正的多线程

📝 课后实战练习题

第1题:自定义迭代器

实现一个EvenNumbers迭代器,接收startend,迭代产生之间的所有偶数。示例:EvenNumbers(1,10)产生2,4,6,8。

第2题:生成器实现range功能

编写生成器函数my_range(start, stop=None, step=1),模仿内置range的行为,支持正负步长。使用yield实现。

第3题:逐行读取并统计

给定一个大文件(模拟),编写生成器read_by_chunk(filepath, chunk_size=1024),每次读取固定字节数(而不是行)。然后用它统计文件总字节数。

第4题:无限序列累积求和

创建生成器cumulative_sum(iterable),接收一个可迭代对象,产出累积和。例如输入[1,2,3,4]输出1,3,6,10。测试用无限生成器作为输入。

第5题:生成器管道重构

将案例4的管道使用yield from或函数组合方式重写,使代码更简洁。

第6题:send实现带状态的计数器

实现生成器counter(start=0),可以通过send重置计数:send('reset')重置为初始值,send('increment')增加1。正常next返回当前值并递增。

第7题:扁平化任意深度嵌套

扩展flatten函数,能够处理嵌套的元组、列表等可迭代对象(但不包含字符串递归展开)。使用isinstance(item, (list, tuple))判断。

🔜 下节课预告

迭代器和生成器是惰性求值的基础。下一节课我们将学习装饰器——Python中另一个强大的函数式编程特性,它可以不修改函数本身而增强其功能。

第32课:装饰器入门与进阶:无参装饰器、有参装饰器底层实战

内容包括:

  • 闭包回顾与装饰器本质
  • 无参装饰器的实现与使用
  • 带参数的装饰器(两层嵌套)
  • 多个装饰器叠加顺序
  • functools.wraps保留元信息
  • 类装饰器
  • 实战:计时器、权限校验、日志记录

装饰器是Python面试和实际项目中的高频知识点,学完你将能写出优雅的增强代码。

🌟 学习鼓励:迭代器和生成器是Python高效处理数据的秘诀。理解它们,你就能写出既内存友好又代码优雅的解决方案。请多动手写生成器,特别是无限序列和数据管道,你会感受到惰性求值的强大之处。


🔗《50节课 Python 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值