01-迭代基础
Python 版本要求:Python 3.11+
贯穿项目:日志分析系统
本节目标:理解 for 循环如何逐行读取大日志文件
概念铺垫
为什么需要理解迭代?
问题场景:大日志文件处理
你需要处理一个 10GB 的日志文件,找出所有错误:
python
# ❌ 一次性读取:内存爆炸
with open("huge.log", "r") as f:
lines = f.readlines() # 10GB 装入内存!程序崩溃
# ✅ 逐行处理:内存友好
with open("huge.log", "r") as f:
for line in f: # 每次只加载一行
if "ERROR" in line:
print(line)问题:为什么 for line in f 可以处理 10GB 文件?背后发生了什么?
生活类比
把迭代想象成音乐播放:
┌─────────────────────────────────────────┐
│ 音乐播放类比 │
├─────────────────────────────────────────┤
│ │
│ 可迭代对象 = 播放列表 │
│ • 存了很多歌 │
│ • 你可以"遍历"它 │
│ • 可以多次播放 │
│ │
│ 迭代器 = 播放指针 │
│ • 知道当前播放到哪一首 │
│ • 每次点击"下一首"就前进 │
│ • 用完就失效(一次性) │
│ │
│ for循环 = 自动播放 │
│ • 自动点击"下一首" │
│ • 播完列表自动停止 │
│ │
│ 关键理解: │
│ • 播放列表 ≠ 播放指针 │
│ • list ≠ iterator │
│ │
└─────────────────────────────────────────┘一句话:
可迭代对象是"播放列表",迭代器是"播放指针"
核心概念
可迭代对象 vs 迭代器
python
from collections.abc import Iterable, Iterator
# 判断类型
my_list = [1, 2, 3]
my_iter = iter(my_list)
print(f"list 可迭代: {isinstance(my_list, Iterable)}") # True
print(f"list 是迭代器: {isinstance(my_list, Iterator)}") # False ← 关键!
print(f"iter 可迭代: {isinstance(my_iter, Iterable)}") # True
print(f"iter 是迭代器: {isinstance(my_iter, Iterator)}") # True关键结论:
| 对象 | 是否可迭代 | 是否迭代器 | 类比 |
|---|---|---|---|
list | ✅ | ❌ | 播放列表 |
str | ✅ | ❌ | 播放列表 |
dict | ✅ | ❌ | 播放列表 |
iter(list) | ✅ | ✅ | 播放指针 |
┌─────────────────────────────────────────────────────────────┐
│ 迭代概念关系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 可迭代对象 │
│ ─────────────────── │
│ • 可以使用 for...in 遍历的对象 │
│ • 实现了 __iter__() 方法 │
│ • 例如:list, tuple, str, dict, set, range │
│ │
│ ▼ iter() 获取 │
│ │
│ 迭代器 │
│ ─────────────── │
│ • 可以被 next() 调用的对象 │
│ • 实现了 __iter__() 和 __next__() 方法 │
│ • 每次返回一个值,直到耗尽 │
│ • 只能用一次,用完即弃 │
│ │
│ ⚠️ 所有迭代器都是可迭代对象 │
│ ⚠️ 但可迭代对象不一定是迭代器 │
│ │
└─────────────────────────────────────────────────────────────┘L1 理解层:会用
for 循环的工作原理
执行流程时间线
┌─────────────────────────────────────────────────────────────┐
│ for 循环执行流程时间线 │
│ │
│ 代码:for line in file: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ① iter(file) ───→ 创建迭代器 │ │
│ │ │ │ │
│ │ ↓ │ │
│ │ ② next(iterator) ─→ 返回第1行 │ │
│ │ │ │ │
│ │ ↓ │ │
│ │ ③ 执行循环体 ───→ 处理第1行 │ │
│ │ │ │ │
│ │ ↓ │ │
│ │ ④ next(iterator) ─→ 返回第2行 │ │
│ │ │ │ │
│ │ ↓ │ │
│ │ ⑤ 执行循环体 ───→ 处理第2行 │ │
│ │ │ │ │
│ │ ... │ │
│ │ ↓ │ │
│ │ ⑥ next(iterator) ─→ StopIteration │ │
│ │ │ │ │
│ │ ↓ │ │
│ │ ⑦ 捕获异常 ─────→ 循环结束 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘for 循环等价展开
你平时写的:
python
numbers = [1, 2, 3]
for num in numbers:
print(num)等价于:
python
numbers = [1, 2, 3]
iterator = iter(numbers) # ① 获取迭代器
while True:
try:
num = next(iterator) # ② 获取下一个
print(num) # ③ 执行循环体
except StopIteration: # ④ 迭代结束
break手动控制迭代
python
numbers = [1, 2, 3]
iterator = iter(numbers)
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# 耗尽后用默认值避免异常
print(next(iterator, "已耗尽")) # "已耗尽"贯穿实战:日志文件处理
python
from typing import Iterator
def filter_errors(filename: str) -> Iterator[str]:
"""过滤日志中的错误行
贯穿项目:日志分析系统第1步
功能:从大日志文件中提取 ERROR 行
内存:无论文件多大,只占用当前行的内存
"""
with open(filename, "r", encoding="utf-8") as f:
for line in f: # 文件对象是迭代器
if "ERROR" in line:
yield line.strip() # 惰性产出
# 使用:处理10GB日志文件
for error in filter_errors("app.log"):
print(error[:100]) # 只打印前100字符关键点:
| 代码 | 含义 |
|---|---|
for line in f | 文件对象是迭代器,逐行读取 |
yield | 生成器,惰性产出结果 |
| 内存占用 | 只占用当前行,与文件大小无关 |
迭代器协议补充
最简示例
python
from typing import Iterator
numbers = [1, 2, 3]
iterator: Iterator[int] = iter(numbers)
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
print(next(iterator, "耗尽")) # "耗尽"迭代器协议详解
┌─────────────────────────────────────────────────────────────┐
│ 迭代器协议 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 可迭代对象必须实现: │
│ ───────────────────── │
│ __iter__() → 返回迭代器对象 │
│ │
│ 迭代器必须实现: │
│ ───────────────────── │
│ __iter__() → 返回 self │
│ __next__() → 返回下一个值或抛 StopIteration │
│ │
│ 示例: │
│ my_list = [1, 2, 3] │
│ hasattr(my_list, '__iter__') # True │
│ hasattr(my_list, '__next__') # False ← 不是迭代器 │
│ │
│ my_iter = iter(my_list) │
│ hasattr(my_iter, '__iter__') # True │
│ hasattr(my_iter, '__next__') # True ← 是迭代器 │
│ │
└─────────────────────────────────────────────────────────────┘L2 实践层:用好
推荐做法
| 做法 | 原因 | 示例 |
|---|---|---|
| 优先用 for 循环 | 自动处理 StopIteration | for item in items: process(item) |
| 大文件逐行处理 | 内存友好 | for line in open('file.txt'): ... |
| 迭代器耗尽后重新获取 | 迭代器是一次性的 | it = iter(items) 每次重新调用 |
| 用 isinstance 检查类型 | 明确区分 | isinstance(obj, Iterator) |
反模式:不要这样做
python
# ❌ 迭代器耗尽后继续使用
numbers = [1, 2, 3]
it = iter(numbers)
print(list(it)) # [1, 2, 3]
print(list(it)) # [] ← 已耗尽!
# ✅ 正确:重新获取
numbers = [1, 2, 3]
print(list(iter(numbers))) # [1, 2, 3]
print(list(iter(numbers))) # [1, 2, 3]python
# ❌ 迭代时修改被迭代对象
items = [1, 2, 3, 4]
for item in items:
if item == 2:
items.remove(item) # 危险!可能遗漏元素
# ✅ 正确:遍历副本
items = [1, 2, 3, 4]
for item in items.copy():
if item == 2:
items.remove(item)适用场景
| 场景 | 是否推荐 | 原因 |
|---|---|---|
| 遍历列表/字典 | ✅ 推荐 | for 循环简洁高效 |
| 处理大文件(GB级) | ✅ 推荐 | 逐行读取,内存友好 |
| 处理无限数据流 | ✅ 推荐 | 惰性计算,按需获取 |
| 多次遍历同一数据 | ❌ 不推荐迭代器 | 迭代器是一次性的 |
| 需要随机访问 | ❌ 不推荐迭代器 | 用列表更合适 |
L3 专家层:深入
迭代器协议实现
python
from collections.abc import Iterable, Iterator
# 检查对象的方法
my_list = [1, 2, 3]
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(是迭代器)
# 手动调用底层方法
it = iter([1, 2, 3])
print(it.__next__()) # 1(等价于 next(it))
print(it.__next__()) # 2
print(it.__next__()) # 3for 循环字节码
python
import dis
def for_loop_example():
for x in [1, 2, 3]:
print(x)
dis.dis(for_loop_example)
# 输出包含 GET_ITER, FOR_ITER 等专门指令┌─────────────────────────────────────────────────────────────┐
│ for 循环字节码 │
├─────────────────────────────────────────────────────────────┤
│ │
│ GET_ITER → 获取迭代器 │
│ FOR_ITER → 调用 next() 并检查 StopIteration │
│ STORE_FAST → 存储当前元素 │
│ ... 循环体 ... │
│ JUMP_BACK → 返回 FOR_ITER │
│ │
└─────────────────────────────────────────────────────────────┘性能考量
| 操作 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
iter(iterable) | O(1) | O(1) | 只创建迭代器对象 |
next(iterator) | O(1) | O(1) | 获取一个元素 |
for item in iterable | O(n) | O(1) | 每次只处理一个元素 |
list(iterator) | O(n) | O(n) | 将所有元素转为列表 |
python
import sys
# 内存对比
list_data = [x for x in range(1_000_000)]
gen_data = (x for x in range(1_000_000))
print(f"列表内存:{sys.getsizeof(list_data) / 1024:.2f} KB") # 约 8 MB
print(f"迭代器内存:{sys.getsizeof(gen_data)} 字节") # 约 200 字节为什么迭代器是一次性的?
┌─────────────────────────────────────────────────────────────┐
│ 迭代器一次性设计原因 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ① 内存效率 │
│ 迭代器不存储所有数据,只维护当前状态指针 │
│ │
│ ② 简化实现 │
│ 无需复杂的回溯和重置逻辑 │
│ │
│ ③ 支持无限序列 │
│ 无限迭代器无法"重置"(没有终点) │
│ │
│ ④ 状态一致性 │
│ 避免多次遍历导致状态混乱 │
│ │
└─────────────────────────────────────────────────────────────┘自检清单
回答以下问题,检查你是否掌握了核心概念:
list是迭代器还是可迭代对象?iter([1,2,3])返回什么?- 为什么迭代器只能用一次?
for line in open('file')为什么可以处理大文件?
答案:
list是可迭代对象,不是迭代器- 返回一个
list_iterator对象 - 迭代器只维护当前位置指针,用完即弃
- 文件对象是迭代器,每次只加载一行到内存
本章能力清单
学完本章,你能够:
- [x] 区分可迭代对象和迭代器
- [x] 理解 for 循环背后的 iter/next 机制
- [x] 用 for 循环逐行处理大文件
- [x] 手动使用 iter() 和 next() 控制迭代
下一步学习:
- 自定义迭代器 → 第2节
- 生成器基础 → 第3节
本章小结
┌─────────────────────────────────────────────────────────────┐
│ 迭代基础 知识要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 核心概念: │
│ ✓ 可迭代对象:播放列表(可以遍历) │
│ ✓ 迭代器:播放指针(用来遍历,一次性) │
│ │
│ for 循环原理: │
│ ✓ iter() 获取迭代器 │
│ ✓ next() 获取下一个元素 │
│ ✓ StopIteration 结束循环 │
│ │
│ 关键区别: │
│ ✓ list 是可迭代对象,iter(list) 是迭代器 │
│ ✓ 迭代器只能用一次,可迭代对象可以多次遍历 │
│ │
│ 应用场景: │
│ ✓ 大文件逐行处理 │
│ ✓ 数据流处理 │
│ ✓ 惰性计算 │
│ │
└─────────────────────────────────────────────────────────────┘