Python高级——迭代器

迭代:一轮接一轮地做,每轮都比上一轮更进一步。

迭代器(Iterator)是 Python 遍历数据的统一机制:它不一次性把数据搬进内存,而是用一个"指针"按需逐个取出元素。理解迭代器,就理解了 for 循环、map、生成器乃至大文件流式处理的内核。


一、为什么需要迭代器

先看一个最直观的对比------读取一个文本文件并逐行处理,有三种写法:

python 复制代码
# part3-pro.md
# 1. 读入文件行列表,再迭代输出
with open('test.txt') as f:
    for line in f.readlines():  # 一次读入整个文件
        if not line : break
        print(line.upper(), end = '')

# 2. 手工迭代读取文件行
with open('test.txt') as f:
    while True:
        line = f.readline()     # 一次读入一行
        if not line : break
        print(line.upper(), end = '')

# 3. 使用迭代器读取文件行
with open('test.txt') as f:
    for line in f:              # 一次读入一行
        print(line.upper(), end = '')

三种方式的本质差别:

方式 内存行为 评价
readlines() 一次性把整个文件塞进内存 文件大到内存装不下时直接崩
readline() 手工循环 一次读一行,内存友好 但要手写 while + 判空,啰嗦
for line in f 迭代器逐行吐出,内部用 C 实现 既省内存又快

核心结论 :迭代器的价值在于惰性(lazy)------数据不在开始时全部到位,而是"要一个给一个"。这正是它能处理超大文件、无限序列的根本原因。

小结:需要遍历、又不想把数据全装进内存时,迭代器是首选。


二、什么是迭代器:两个协议对象

迭代器机制建立在两个概念之上:

可迭代对象(Iterable) :实现了 __iter__() 方法的对象。该方法返回一个迭代器对象 ,可被 iter() 调用。

迭代器对象(Iterator) :实现了 __next__() 方法的对象。该方法返回下一个元素,结束时抛出 StopIteration,可被 next() 调用。

把迭代器想成"容器上的指针" :初始指向第一个元素;每次调用 __next__() 指针前移一位;指向末尾时抛出 StopIteration

迭代器不是容器本身,但有些容器自己也实现了迭代器协议,它同时就是迭代器。
#mermaid-svg-XKuWNv4pfFritU1g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XKuWNv4pfFritU1g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XKuWNv4pfFritU1g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XKuWNv4pfFritU1g .error-icon{fill:#552222;}#mermaid-svg-XKuWNv4pfFritU1g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XKuWNv4pfFritU1g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XKuWNv4pfFritU1g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XKuWNv4pfFritU1g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XKuWNv4pfFritU1g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XKuWNv4pfFritU1g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XKuWNv4pfFritU1g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XKuWNv4pfFritU1g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XKuWNv4pfFritU1g .marker.cross{stroke:#333333;}#mermaid-svg-XKuWNv4pfFritU1g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XKuWNv4pfFritU1g p{margin:0;}#mermaid-svg-XKuWNv4pfFritU1g .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XKuWNv4pfFritU1g .cluster-label text{fill:#333;}#mermaid-svg-XKuWNv4pfFritU1g .cluster-label span{color:#333;}#mermaid-svg-XKuWNv4pfFritU1g .cluster-label span p{background-color:transparent;}#mermaid-svg-XKuWNv4pfFritU1g .label text,#mermaid-svg-XKuWNv4pfFritU1g span{fill:#333;color:#333;}#mermaid-svg-XKuWNv4pfFritU1g .node rect,#mermaid-svg-XKuWNv4pfFritU1g .node circle,#mermaid-svg-XKuWNv4pfFritU1g .node ellipse,#mermaid-svg-XKuWNv4pfFritU1g .node polygon,#mermaid-svg-XKuWNv4pfFritU1g .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XKuWNv4pfFritU1g .rough-node .label text,#mermaid-svg-XKuWNv4pfFritU1g .node .label text,#mermaid-svg-XKuWNv4pfFritU1g .image-shape .label,#mermaid-svg-XKuWNv4pfFritU1g .icon-shape .label{text-anchor:middle;}#mermaid-svg-XKuWNv4pfFritU1g .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XKuWNv4pfFritU1g .rough-node .label,#mermaid-svg-XKuWNv4pfFritU1g .node .label,#mermaid-svg-XKuWNv4pfFritU1g .image-shape .label,#mermaid-svg-XKuWNv4pfFritU1g .icon-shape .label{text-align:center;}#mermaid-svg-XKuWNv4pfFritU1g .node.clickable{cursor:pointer;}#mermaid-svg-XKuWNv4pfFritU1g .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XKuWNv4pfFritU1g .arrowheadPath{fill:#333333;}#mermaid-svg-XKuWNv4pfFritU1g .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XKuWNv4pfFritU1g .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XKuWNv4pfFritU1g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XKuWNv4pfFritU1g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XKuWNv4pfFritU1g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XKuWNv4pfFritU1g .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XKuWNv4pfFritU1g .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XKuWNv4pfFritU1g .cluster text{fill:#333;}#mermaid-svg-XKuWNv4pfFritU1g .cluster span{color:#333;}#mermaid-svg-XKuWNv4pfFritU1g div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XKuWNv4pfFritU1g .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XKuWNv4pfFritU1g rect.text{fill:none;stroke-width:0;}#mermaid-svg-XKuWNv4pfFritU1g .icon-shape,#mermaid-svg-XKuWNv4pfFritU1g .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XKuWNv4pfFritU1g .icon-shape p,#mermaid-svg-XKuWNv4pfFritU1g .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XKuWNv4pfFritU1g .icon-shape .label rect,#mermaid-svg-XKuWNv4pfFritU1g .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XKuWNv4pfFritU1g .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XKuWNv4pfFritU1g .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XKuWNv4pfFritU1g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用 iter()
next()
next()
next()
next()无元素时
**可迭代对象(Iterable)**
**迭代器对象(Iterator)**
元素 1
元素 2
元素 3
...
StopIteration

异常

for 循环到底做了什么

很多人以为 for 是"遍历列表"的特例,其实它是迭代器协议的语法糖。for x in obj: 等价于:

python 复制代码
# 自定义示例:for 循环的等价展开
it = iter(obj)          # 1. 拿到迭代器
while True:
    try:
        x = next(it)    # 2. 逐个取值
        # ... 循环体 ...
    except StopIteration:
        break           # 3. 取完就停

这就是为什么任何实现了迭代器协议的对象都能用 for 遍历 ------列表、字典、字符串、文件、生成器,无一例外。for 从不为某类数据类型开小灶。

isinstance 验证身份

python 复制代码
# part3-pro.md
from collections.abc import Iterable, Iterator

print(isinstance([1, 2, 3], Iterable))   # True:列表是可迭代对象
print(isinstance([1, 2, 3], Iterator))   # False:列表本身不是迭代器
print(isinstance(iter([1, 2, 3]), Iterator))  # True:iter() 返回迭代器

小结:Iterable 有"产出迭代器的能力",Iterator 额外有"主动吐出下一个元素"的能力。


三、手工调用与自动迭代

源笔记给出的手工迭代案例,正好演示了"指针"如何一步步走:

python 复制代码
# part3-pro.md
# 1. 列表迭代器
l = [1, 2, 3, 4, 5]
it = iter(l)
# it = l.__iter__() # 另一种方式

try:
    while True:
        item = next(it)
        print(item, end=', ')
except StopIteration as e:
    print('end')

# 2. 字典迭代器
d = {'name' : 'z3', 'age' : 18, 'sex' : 'male'}
it = iter(d)
try:
    while True:
        key = next(it)
        print(key, '-->', d[key])
except StopIteration as e:
    print(e)
    print(type(e))

输出:

text 复制代码
1, 2, 3, 4, 5, end
name --> z3
age --> 18
sex --> male
<class 'StopIteration'>

而平时写的 for 循环,就是上面这段"手工 iter + next + 捕获 StopIteration"的自动版:

python 复制代码
# part3-pro.md
# 1. 列表迭代器
l = [1, 2, 3, 4, 5]
for e in l:
    print(e, end = ", ")
print('end\n')

# 2. 字典迭代器
d = {'name' : 'z3', 'age' : 18, 'sex' : 'male'}
for key in d:
    print(key, '-->', d[key])

小结:手工 next() 让你看清迭代全过程;for 只是替你包好了 StopIteration


四、自定义迭代器:把协议落到实处

只理解内置类型不够。真正掌握迭代器,是自己写一个迭代器类。规则很简单:

  • __iter__(self) 返回迭代器自身(通常是 return self);
  • __next__(self) 返回下一个值,没值了就 raise StopIteration

下面用斐波那契数列做例子(只产出不超过 max 的项):

python 复制代码
# 自定义示例:手写一个斐波那契迭代器
class Fib:
    def __init__(self, max):
        self.max = max
        self.a, self.b = 0, 1

    def __iter__(self):
        return self            # 迭代器返回自身

    def __next__(self):
        if self.a > self.max:
            raise StopIteration # 取完了就抛异常
        result = self.a
        self.a, self.b = self.b, self.a + self.b
        return result

for n in Fib(20):
    print(n, end=' ')         # 0 1 1 2 3 5 8 13

注意 __iter__ 返回 self 这个细节:它让"可迭代对象"和"迭代器"合二为一,所以 Fib 的对象既能被 for 遍历,也能被 next() 直接驱动。


五、迭代器的两个关键特性

1. 惰性求值(Lazy Evaluation)

迭代器不预先生成所有元素,而是调用一次 __next__ 才计算一次。这意味着:

  • 遍历一个亿级元素的序列,内存里只存"当前这一个";
  • 可以表示无限序列(如自然数流),因为从不需要"全部生成"。

2. 一次性消耗(一次性流过的水流)

迭代器是有状态的:指针走过就回不去了。同一个迭代器对象遍历一遍后就"耗尽",再遍历得到空:

python 复制代码
# 自定义示例:迭代器只能走一遍
it = iter([1, 2, 3])
print(list(it))   # [1, 2, 3]
print(list(it))   # []  ------ 已经耗尽

# 可迭代对象(如列表)每次 iter() 都重新造一个迭代器,所以能反复遍历
lst = [1, 2, 3]
print(list(iter(lst)))  # [1, 2, 3]
print(list(iter(lst)))  # [1, 2, 3]

这正是"可迭代对象"和"迭代器"的实战分水岭:可迭代对象可重复遍历,迭代器一次性

小结:惰性让它省内存,一次性让它有状态------两者一体两面。


六、生成器:更优雅的迭代器

每次手写 __iter__ / __next__ 太啰嗦。Python 给了"生成器(Generator)":只要函数里有 yield,它就自动变成一个迭代器,状态机由解释器替你维护。

python 复制代码
# 自定义示例:用生成器重写斐波那契
def fib(max):
    a, b = 0, 1
    while a <= max:
        yield a               # 每次走到这里"暂停并返回"
        a, b = b, a + b

for n in fib(20):
    print(n, end=' ')         # 0 1 1 2 3 5 8 13

对比第四节的 Fib 类:逻辑一字不差,但生成器省掉了全部样板代码。生成器本质就是一种迭代器

python 复制代码
# 自定义示例:生成器是迭代器
from collections.abc import Iterator
print(isinstance(fib(20), Iterator))   # True

yield 的语义是"产出并暂停"------下次 next() 从暂停处继续,局部变量原样保留。这正是迭代器"指针"思想的自动实现。


七、容器自身是迭代器吗

并非所有容器都"兼职"迭代器。看源笔记的对照:

python 复制代码
# part3-pro.md
# 1. 迭代器对象不是集合
single = range(1, 10)
# next(single)    # TypeError: 'range' object is not an iterator

it1 = iter(single)  # 返回一个迭代器
print(next(it1), next(it1), next(it1))
it2 = iter(single)  # 返回新的迭代器
print(it1 is it2)   # False
print(next(it2), next(it2), next(it2))

# 2. 迭代器对象是集合本身
with open('example.txt', 'r') as f:
    print(hasattr(f, '__iter__'))  # True
    print(hasattr(f, '__next__'))  # True
    print(iter(f) is f)            # True,说明它自身就是迭代器

关键区分:

对象 是 Iterable 自身是 Iterator 说明
list / str / dict 每次 iter() 造新迭代器,可重复遍历
range 同上,next(range) 直接报错
文件对象 f iter(f) is f,它自己就是迭代器
生成器 自身即迭代器,一次性

小结:iter(f) is fTrue,是判断"容器自身是不是迭代器"的硬标准。


八、常用迭代函数

迭代器不是孤例,Python 标准库里一票函数都返回迭代器,构成"迭代生态":

python 复制代码
# part3-pro.md + 自定义示例
# 1. map:对每个元素套函数,返回迭代器
print(list(map(str.upper, ['a', 'b'])))     # ['A', 'B']

# 2. filter:保留满足条件的元素
print(list(filter(lambda x: x % 2 == 0, range(6))))  # [0, 2, 4]

# 3. zip:并行打包多个可迭代对象
print(list(zip('ab', [1, 2])))              # [('a', 1), ('b', 2)]

# 4. enumerate:带下标迭代
for i, v in enumerate(['x', 'y']):
    print(i, v)                             # 0 x  /  1 y

map / filter / zip / enumerate 全部返回惰性迭代器,不会立刻算出全部结果------这就是它们高效的原因。

itertools 模块进一步提供了无限/组合型迭代器,例如:

python 复制代码
# 自定义示例:itertools 的惰性迭代器
import itertools

# count:无限自然数流(需 islice 截断,否则停不下来)
print(list(itertools.islice(itertools.count(), 5)))   # [0, 1, 2, 3, 4]

# chain:把多个可迭代对象串成一条
print(list(itertools.chain([1, 2], [3, 4])))         # [1, 2, 3, 4]

小结:凡是返回迭代器的函数,都天然支持惰性、可串成数据处理管道。


九、常见坑位

现象 真相
for 遍历完再遍历一次,啥也没有 那个对象(如生成器、文件对象)是迭代器,一次性耗尽 ;列表则没事,因为每次 iter() 都重建
next(range(5)) 直接 TypeError range 是可迭代对象,不是迭代器 ,得先 iter(range(5))
for 循环里 list.remove(x) 会漏元素 边遍历边改原容器破坏迭代位置;应遍历副本或用推导式重建
自定义 __next__ 里直接 return 当结束 应该用 raise StopIterationreturn 在迭代器里等价于抛 StopIteration(效果相同,但显式更清晰)
生成器里 raise StopIteration 想提前结束 Python 3.7+ 受 PEP 479 约束,生成器内的 StopIteration 会转为 RuntimeError;应改用 return 结束生成器

全文小结

  • 迭代器 = 惰性指针__iter__ 产出迭代器,__next__ 逐个取值,取完抛 StopIteration
  • for 是语法糖 :底层就是 iter() + next() + 捕获 StopIteration,所以任何可迭代对象都能 for
  • 两类对象分清楚:Iterable 可重复遍历、自身非迭代器(list/str/dict/range);Iterator 一次性、自身即指针(文件对象、生成器)。
  • 想省事写迭代器,用生成器yield 让函数自动成为迭代器,免去 __iter__/__next__ 样板。
  • 惰性 + 一次性是迭代器的双刃剑:省内存、能表达无限序列,但同一迭代器不能反复遍历。
相关推荐
初级代码游戏1 小时前
iOS开发 Swift 速记4:函数与闭包
开发语言·ios·swift
math_hongfan1 小时前
鸿蒙Agent高级技能自定义开发:自定义意图服务/能力注册/Agent技能编排/服务插件开发实战
android·学习·华为·harmonyos·鸿蒙
郭涤生1 小时前
C++ 零拷贝(Zero-Copy)
linux·开发语言·c++
老白干1 小时前
基于 Spring AOP 的操作日志记录:以 DeptController 增删接口为例
java·python·spring
回忆2012初秋1 小时前
C# 中的 EAP:基于事件的异步编程全面指南(一)
开发语言·c#
ty_xiumud1 小时前
以太网 MAC 详解:帧格式、位序、FCS 与收发流程
开发语言·macos·php
John jj1 小时前
拆解 Telegram 群组频道收录市场:三类方案,一个可运行的评分模型,目前TG中文人工评分加模型评分机制——LetsTG收录“快速”、“无门槛”
大数据·后端·python·深度学习·搜索引擎·django·全文检索