当前位置: 首页 > news >正文

2025-10-06 Python不基础 9——迭代器与生成器

文章目录

  • 1. 迭代器(Iterator)
    • 1.1. Iterable vs. Iterator
    • 1.2. for loop 执行流程
    • 1.3. 自定义可迭代对象(链表)
    • 1.4. 迭代器为何要实现 `__iter__()`?
  • 2. 生成器(Generator)
    • 2.1. `yield` 关键字
    • 2.2. 生成器 vs 迭代器
    • 2.3. `send()` 方法
    • 2.4. `return` 关键字

本文参考视频链接:

  • https://www.bilibili.com/video/BV1ca411t7A9
  • https://www.bilibili.com/video/BV1KS4y1D7Qb

1. 迭代器(Iterator)

在 Python 中,for loop 能遍历多种数据结构(如 listdict、文件对象),看似直观,实则依赖两个核心概念——可迭代对象(Iterable)迭代器(Iterator)。例如:

  • list 是有序结构,可按索引遍历;
  • dict 是无序结构,无索引却能被遍历;
  • 文件对象是复杂结构,无“第 N 个元素”概念,仍能被遍历。

这些现象的本质是:所有能被 for loop 遍历的对象,都是“可迭代对象”,而 for loop 的底层是通过“迭代器”实现元素逐个获取的

1.1. Iterable vs. Iterator

要理解 for loop 的实现,必须先明确两者的定义、区别与联系。

概念官方定义(Python 文档)典型示例
可迭代对象(Iterable)能“逐个返回自身成员”的对象,支持在 for loop 中使用,本质是“可产生迭代器的对象”。liststrdict、文件对象
迭代器(Iterator)表示“数据流”的对象,支持通过 next() 函数逐个获取数据,遍历到末尾时抛出 StopIteration 异常。iter(list) 返回的对象、文件迭代器
维度可迭代对象(Iterable)迭代器(Iterator)
角色定位数据的“保存者/容器”(如 list 存储元素)数据的“遍历者/状态持有者”(记录遍历进度)
状态性无状态(不知道遍历到哪一步,仅存储数据)有状态(明确知道当前遍历位置,下次从哪里继续)
核心能力能产生迭代器(通过 iter() 函数)能通过 next() 函数返回下一个元素
容器接口通常实现容器接口(如 list 可通过索引修改元素)无容器接口(仅负责遍历,不支持修改数据)

Python 通过“魔术方法”区分两者,具体要求如下:

概念必须实现的魔术方法作用说明
可迭代对象(Iterable)满足以下任一条件:
1. 实现 __iter__() 方法(返回一个迭代器)
2. 实现 __getitem__() 方法(支持按索引访问,如序列)
__iter__():核心接口,通过 iter(iterable) 调用,返回迭代器;
__getitem__():兼容旧序列类型(如自定义序列)。
迭代器(Iterator)必须同时实现:
1. __next__() 方法(返回下一个元素,末尾抛 StopIteration
2. __iter__() 方法(返回自身,使迭代器也是可迭代对象)
__next__():核心接口,通过 next(iterator) 调用,获取下一个元素;
__iter__():官方要求,确保迭代器可被 for loop 直接遍历。
image-20251006132351098

1.2. for loop 执行流程

for loop 的本质是“自动获取迭代器 → 循环调用 next() → 捕获 StopIteration 终止”,具体步骤如下:

  1. 获取迭代器:对 for loopin 后的“可迭代对象”调用 iter(iterable),生成对应的“迭代器”(记为 it)。
    • 底层字节码层面:通过 GET_ITER 指令完成(视频中提及的 get iter 操作)。
  2. 循环获取元素:反复调用 next(it),将返回值赋值给 for loop 的循环变量(如 for item in ... 中的 item)。
  3. 终止循环:当迭代器遍历到末尾时,next(it) 抛出 StopIteration 异常,for loop 捕获该异常并自动终止(无需手动处理)。

以下代码模拟了 for loop 的底层逻辑(等价于 for item in [1,2,3]):

# 1. 获取可迭代对象
iterable = [1, 2, 3]
# 2. 生成迭代器
it = iter(iterable)  # 调用 iterable.__iter__()
# 3. 循环调用 next() 并捕获异常
try:while True:item = next(it)  # 调用 it.__next__()print(item)      # 模拟 for loop 内的逻辑
except StopIteration:# 遍历结束,自动退出循环pass

1.3. 自定义可迭代对象(链表)

Python 无内置链表(link list),但可通过“可迭代对象 + 迭代器”实现,使其支持 for loop 遍历。

  • 链表由“节点(Node)”组成,每个节点包含 name(数据)和 next_node(下一个节点的引用);
  • 要求:通过 for node in 链表头节点,遍历所有节点并获取 name

① 链表节点类(可迭代对象:Iterable)

需实现 __iter__() 方法,返回链表的迭代器。

class Node:def __init__(self, name):self.name = name          # 节点数据self.next_node = None     # 指向下一个节点的引用# 关键:实现 __iter__(),使 Node 成为可迭代对象def __iter__(self):# 返回链表的迭代器(NodeIterator),传入当前节点(链表头)return NodeIterator(self)

② 链表迭代器类(Iterator)

需实现 __next__()(核心遍历逻辑)和 __iter__()(满足官方要求)。

class NodeIterator:def __init__(self, head_node):# 初始化:当前遍历到的节点(从链表头开始)self.current_node = head_node# 核心:实现 __next__(),返回下一个节点def __next__(self):# 1. 若 current_node 为 None,说明遍历结束,抛出 StopIterationif self.current_node is None:raise StopIteration# 2. 保存当前节点(待返回)current = self.current_node# 3. 移动到下一个节点(更新状态)self.current_node = self.current_node.next_node# 4. 返回当前节点return current# 关键:实现 __iter__(),返回自身,使迭代器也是可迭代对象def __iter__(self):return self

③ 测试过程与结果

# 1. 构建链表:node1 → node2 → node3
node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")
node1.next_node = node2  # node1 的下一个是 node2
node2.next_node = node3  # node2 的下一个是 node3# 2. 用 for loop 遍历链表(node1 是可迭代对象)
for node in node1:print(node.name)  # 输出:node1 → node2 → node3
image-20251006132704325
  • 迭代器状态NodeIterator 通过 current_node 记录遍历进度,每次调用 __next__() 后更新为下一个节点;
  • 终止条件:当 current_nodeNone(链表末尾),抛出 StopIterationfor loop 自动终止;
  • 可迭代性Node 实现 __iter__() 成为可迭代对象,NodeIterator 实现 __iter__() 成为可迭代对象,确保能被 for loop 遍历。

1.4. 迭代器为何要实现 __iter__()

为何迭代器必须实现 __iter__()?,需从“官方要求”和“实际场景”两方面解释。

① 官方要求:迭代器必须是可迭代对象

Python 官方文档规定:迭代器(Iterator)必须实现 __iter__() 方法,且返回自身
原因:确保迭代器能被用于所有“需要可迭代对象”的场景(如 for loopzip()map() 等),符合“直觉一致性”。

image-20251006132815435

例如,若迭代器未实现 __iter__(),直接放入 for loop 会报错:

# 错误案例:迭代器未实现 __iter__()
class BadIterator:def __init__(self, data):self.data = dataself.index = 0def __next__(self):if self.index >= len(self.data):raise StopIterationval = self.data[self.index]self.index += 1return val# 生成错误的迭代器
bad_it = BadIterator([1,2,3])
# 尝试用 for loop 遍历:报错(BadIterator 不是可迭代对象)
for val in bad_it:  # TypeError: 'BadIterator' object is not iterableprint(val)

② 修正方案:__iter__() 返回自身

只需在迭代器中添加 __iter__() 并返回 self,即可解决问题:

class GoodIterator:def __init__(self, data):self.data = dataself.index = 0def __next__(self):if self.index >= len(self.data):raise StopIterationval = self.data[self.index]self.index += 1return val# 关键:实现 __iter__(),返回自身def __iter__(self):return self# 生成正确的迭代器
good_it = GoodIterator([1,2,3])
# 可直接用 for loop 遍历:正常输出 1 → 2 → 3
for val in good_it:print(val)

CPython 本身并非完全遵循“迭代器必须是可迭代对象”的要求,但这属于底层实现细节,日常开发中仍建议严格实现 __iter__(),原因如下:

  • 确保代码兼容性(跨 Python 解释器,如 PyPy、Jython);
  • 符合 Python 设计哲学(“显式优于隐式”“一致性”);
  • 避免因底层差异导致的潜在 Bug。

2. 生成器(Generator)

生成器的核心定位是**“特殊的迭代器”**——它完全遵循迭代器协议(支持 next() 调用、触发 StopIteration 终止),但在定义方式、状态管理上更简洁。
前置要求:建议先理解迭代器(__iter__()/__next__()、状态保存、StopIteration),否则难以理解生成器的“简化本质”。

生成器与迭代器的核心共性(视频验证):

  • 均可通过 for loop 遍历(如 for i in generator: ...);
  • 均可通过 next() 函数逐个获取元素;
  • 遍历结束时均会触发 StopIteration 异常(无需手动捕获)。

这是生成器最易混淆的两个概念,必须明确区分,具体差异如下:

概念定义与特征示例代码关键说明
生成器函数1. 用 def 定义,但包含 yield 关键字;
2. 编译期被 Python 标记为“生成器函数”(非普通函数);
3. 调用后不执行函数体,而是返回生成器对象。
def gen(n): yield n本质是“生成器的模板”,负责定义元素生成逻辑。
生成器对象1. 调用生成器函数的返回值(如 g = gen(5));
2. 实现迭代器协议,可通过 next()/for 遍历;
3. 保存函数运行状态(暂停位置、局部变量)。
g = gen(5)本质是“可迭代的对象”,负责执行生成逻辑并返回元素。
  • 普通函数调用:def add(a,b): return a+b → 调用 add(1,2) 直接返回结果 3
  • 生成器函数调用:def gen(n): yield n → 调用 gen(5) 不返回 5,而是返回生成器对象 g,仅当调用 next(g) 时才执行函数体。

2.1. yield 关键字

yield 是生成器的“灵魂”,其核心作用是**“暂停函数执行、保存状态、返回当前值”**,替代了传统迭代器中 __next__() 的手动状态管理。

# 生成器函数
def gen(n):while n > 0:yield n  # 暂停点:返回n,保存状态n -= 1# 隐含 return,触发 StopIteration# 1. 调用生成器函数:生成生成器对象,不执行函数体
g = gen(5)  
print(type(g))  # 输出:<class 'generator'>(非函数,非int)# 2. 第一次调用 next():执行函数体直到 yield
print(next(g))  # 输出:5(执行到 yield 5,暂停,n 仍为5)# 3. 第二次调用 next():从暂停处继续执行
print(next(g))  # 输出:4(先执行 n -=1 → n=4,再执行 yield 4,暂停)# 4. for loop 遍历剩余元素(等价于反复调用 next())
for i in g:print(i)  # 输出:3 → 2 → 1(遍历结束后触发 StopIteration)
image-20251006133310790

执行流程总结

  1. 初始化g = gen(5) → 创建生成器对象,保存参数 n=5,函数体未执行;
  2. 首次 next():执行函数体→进入 while 循环→yield 5→返回 5,暂停于 yield 处(记住当前 n=5、循环状态);
  3. 后续 next():从暂停处恢复→执行 n -=1→再次进入循环→yield n→返回新值,再次暂停;
  4. 终止:当 n=0 时退出 while 循环→执行隐含的 return→触发 StopIterationfor 循环捕获异常并终止。

生成器的“暂停/恢复”依赖 Python 字节码中的 YIELD_VALUE 指令,核心逻辑:

  • 执行到 YIELD_VALUE 时:
    1. 将当前要返回的值(如 5)存入“返回值缓冲区”;
    2. 保存当前函数帧(frame)的状态(包括局部变量 n、程序计数器“下一条要执行的指令”);
    3. 退出函数帧,返回值给 next() 调用者;
  • 下次 next() 时:
    1. 恢复之前保存的函数帧状态;
    2. 从“下一条指令”继续执行(如 n -=1)。

简言之:yield 替代了传统迭代器中“手动用实例变量保存状态”的逻辑,让 Python 自动管理函数运行状态。

image-20251006133439433

2.2. 生成器 vs 迭代器

以“链表遍历”为例,传统迭代器需定义类并实现 __iter__()/__next__(),而生成器仅需用 yield 即可实现相同功能。

class Node:def __init__(self, name):self.name = nameself.next_node = Nonedef __iter__(self):# 生成器函数:自动实现迭代器协议current = self  # 局部变量保存状态(无需实例变量)while current is not None:yield current  # 暂停并返回当前节点,自动保存状态current = current.next_node  # 恢复后更新状态# 测试(与传统迭代器完全一致)
node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")
node1.next_node = node2
node2.next_node = node3for node in node1:print(node.name)  # 输出:node1 → node2 → node3
维度传统迭代器生成器
代码量需定义两个类(可迭代对象+迭代器),代码冗余仅需一个生成器函数(yield),代码简洁
状态管理需手动用实例变量保存状态(如 self.currentPython 自动通过函数帧保存状态(局部变量、暂停位置)
迭代器协议实现需手动实现 __iter__()/__next__()自动实现(生成器对象内置这两个方法)
可读性逻辑分散(类间跳转),不易理解逻辑集中(一个函数内),符合直觉

2.3. send() 方法

send() 是生成器独有的功能(普通迭代器无此方法),核心作用是**“在迭代过程中向生成器传递值,改变其内部状态”**,实现“双向通信”。

send()next() 的关系:

  • next(g) 等价于 g.send(None)(首次调用生成器时,必须传递 None,否则报错);
  • g.send(value):在恢复生成器执行时,将 value 作为 yield 表达式的返回值,赋值给变量。
# 支持 send() 的生成器函数
def gen(n):while n > 0:# yield 表达式:接收 send() 传递的值,赋值给 temptemp = yield n  if temp is not None:  # 若传递了值,更新 nn = tempn -= 1# 1. 初始化生成器对象
g = gen(5)# 2. 首次调用:必须用 send(None) 或 next()
first = g.send(None)  # 等价于 next(g)
print(first)  # 输出:5(执行到 yield 5,暂停,temp 未赋值)# 3. 传递值给生成器:改变 n 的值
second = g.send(10)  # 恢复执行→temp=10→n=10→n-=1=9→yield 9→返回9
print(second)  # 输出:9# 4. for loop 遍历(等价于反复调用 g.send(None))
for i in g:print(i)  # 输出:8 → 7 → ... → 1
image-20251006133711462
  • 首次调用限制:生成器未执行到 yield 时(初始化后),send() 不能传递非 None 值,否则报错(因为没有 yield 表达式接收值);
  • yield 无赋值时的处理:若 yield 未赋值给变量(如 yield n 而非 temp = yield n),send() 传递的值会被忽略,但生成器仍会正常恢复执行;
  • 用途:用于动态调整生成器的内部逻辑(如视频中修改 n 的值,改变迭代次数),实现“外部控制生成器”。

2.4. return 关键字

生成器中的 return 与普通函数完全不同,核心规则:

  1. 触发 StopIteration:生成器中执行 return(无论是否带值),都会立即触发 StopIteration 异常,终止迭代;
  2. 返回值的获取方式return 的值不会直接返回给 next()/send(),而是作为 StopIteration 异常的 value 属性,需通过捕获异常获取。
def gen(n):while n > 0:yield nn -= 1return "迭代结束"  # return 值存入 StopIteration.valueg = gen(3)try:while True:print(next(g))  # 输出:3 → 2 → 1
except StopIteration as e:print(e.value)  # 输出:"迭代结束"(获取 return 值)
http://www.dtcms.com/a/451218.html

相关文章:

  • 物业管理系统需求分析自助建站优化排名
  • UNIX下C语言编程与实践30-UNIX 进程控制:vfork-exec 模型与 fork-exec 模型的效率对比
  • 网站开发讲座不属于网站后期维护
  • 网站建设与管理课程实训哈尔滨智能建站模板
  • 凭祥网站建设盐城网站建设定制
  • 江苏中盛建设集团网站网站怎么做透明导航栏
  • 太仓智能网站建设服装电子商务网站建设
  • 潍坊公司网站制作微信微商城怎么开通
  • 网站开发建设项目服务清单一级a做爰片免费网站
  • wordpress建站环境搭建wordpress生成app
  • 公司创建的法制网站邢台163信息港
  • python做网站难么优化关键词快速排名
  • 鸿蒙实现滴滴出行项目
  • 外贸网站做哪些语言wordpress后太慢
  • 自己如何做网站优化网站购物车实现
  • 一个网站一年的费用多少国际军事最新军事新闻
  • 知名的中文域名网站有哪些阜阳手机网站建设
  • 鞍山制作网站河北百度推广电话
  • 贵州大地建设集团网站wordpress文件类型不支持
  • 网站备案信息核验单填写ppt做视频的模板下载网站有哪些内容
  • 如何通过psd做网站微信小程序开发要多少钱
  • 望城区城市建设投资集团门户网站办公楼装修设计
  • 高端上海网站设计公司价格文化网站策划
  • 南宁网站建设公司哪个好豆瓣wordpress主题
  • 做app的模板下载网站有哪些内容竞价交易规则
  • 江苏网站建设企业德州核酸检测最新公告
  • 鸟哥的Linux私房菜:第二部分Linux文件目录与磁盘格式总结1
  • 广元做网站的公司建材团购网站建设方案
  • 做网站所需的知识技能家装用什么软件设计
  • 网站建设营销怎么做网站建设属于广告费吗