Python 迭代器与可迭代对象详解
在 Python 开发中,「遍历」是最常见的操作之一——不管是扫描列表、查看字典键值对,还是逐行读取文件,我们都习惯直接写一个 for ... in ...。但你有没有好奇过:为什么这些东西能放进 for 循环?为什么有些对象可以直接用 next() 取元素? 答案就藏在两个核心概念里:「可迭代对象(Iterable)」和「迭代器(Iterator)」。
可迭代对象 (Iterable)
简单来说,可迭代对象就是“能被 for 循环遍历的对象”。它本身并不直接产生“下一个值”的能力,但可以“变出”一个能逐个产出元素的工具——也就是迭代器。
Python 里常见的可迭代对象可以分为两类:
- 基础集合类:
list、tuple、dict、set、str、bytes 等
- 带延迟特性的对象:生成器(generator)、文件对象、
zip()/map()/filter() 的返回值等
怎么判断是不是可迭代的?
使用 isinstance() 配合标准库中的 collections.abc.Iterable:
from collections.abc import Iterable
# 基础集合类型
print(isinstance([1, 2], Iterable)) # True
print(isinstance({'a': 1}, Iterable)) # True
print(isinstance('hello', Iterable)) # True
# 延迟对象
print(isinstance((x for x in range(3)), Iterable)) # True
print(isinstance(open('test.txt', 'r'), Iterable)) # True(别忘了关闭文件!)
# 非可迭代类型
print(isinstance(100, Iterable)) # False
print(isinstance(True, Iterable)) # False
迭代器 (Iterator)
迭代器是可迭代对象的“进阶版”——它不仅可以进入 for 循环,还能被 next() 主动调用,逐个取出元素,直到耗尽并抛出 StopIteration 异常。可以把它理解为“手握数据流控制权的工具”。
怎么判断是不是迭代器?
同样使用 isinstance(),这次搭配 collections.abc.Iterator:
from collections.abc import Iterator
# 生成器是迭代器!
print(isinstance((x for x in range(3)), Iterator)) # True
# 通过 iter() 转换后得到的也是迭代器
print(isinstance(iter([]), Iterator)) # True
# 基础集合本身不是迭代器!
print(isinstance([1, 2], Iterator)) # False
print(isinstance('hello', Iterator)) # False
从可迭代对象得到迭代器
使用内置函数 iter() 即可轻松转换:
nums = [1, 2, 3]
it_nums = iter(nums) # 拿到迭代器
print(next(it_nums)) # 1
print(next(it_nums)) # 2
print(next(it_nums)) # 3
print(next(it_nums)) # 抛出 StopIteration 异常!
注意:迭代器非常“懒”,只有当你调用 next() 时它才会计算并返回下一个值,这为处理大数据量或无限序列提供了可能。
迭代器 vs 可迭代对象:到底差在哪?
有时单看文字容易绕,我们用一张对比表和实际场景把区别讲透:
举个最直观的无限自然数序列的例子:
- 用列表实现?不可能——
[0, 1, 2, …] 早晚把内存写爆。
- 用迭代器实现?轻轻松松(后面马上会自定义一个给你看)。
再看一个内存占用对比的例子(在 CPython 下,整数的内存开销因小整数池等因素而有所不同,但这里主要看趋势):
# 生成 100 万个平方数的列表 —— 需要提前分配大量内存
big_list = [x*x for x in range(1_000_000)]
# 生成 100 万个平方数的迭代器 —— 只占用几十字节!
big_iterator = (x*x for x in range(1_000_000))
你甚至可以在只创建迭代器后立即用 next() 按需取值,完全不用等全部生成完。
for 循环的“真面目”
你写的每一行 for x in xxx,Python 底层都会自动完成这三件事:
- 调用
iter(xxx) 获取对应的迭代器对象
- 循环调用
next(迭代器),并将结果赋值给 x
- 一旦捕获到
StopIteration 异常,就优雅地退出循环
比如这段简单的代码:
for num in [1, 2, 3]:
print(num)
Python 内部其实等价于执行以下逻辑:
# 第一步:获取迭代器
it = iter([1, 2, 3])
# 第二步 + 第三步:循环取值 + 异常退出
while True:
try:
num = next(it)
print(num)
except StopIteration:
break
现在是不是觉得 for 循环没那么“神秘”了?
实用开发建议
1. 处理大数据/无限序列,优先考虑迭代器或生成器
当你要处理几十万、上百万行的文件,或者要遍历无限序列时,请绕开列表推导式,改用生成器表达式或自定义迭代器,否则内存很快就会被撑爆。
# ❌ 一次性把整个文件读进内存(危险)
lines = [line.strip() for line in open('huge_file.log')]
# ✅ 用生成器表达式按行处理(安全)
lines_safe = (line.strip() for line in open('huge_file.log'))
for line in lines_safe:
# 逐行处理
pass
2. 自定义迭代器,只需要两个方法
如果你想自己写一个迭代器类(比如实现刚才提到的无限自然数),必须实现两个特殊方法:
__iter__():必须返回迭代器自身(self),这是迭代器与可迭代对象在协议上保持统一的关键
__next__():负责“取出当前元素、移动到下一个元素、或在结束时抛出 StopIteration”
直接上代码,实现一个从指定起点开始的无限自然数迭代器:
from collections.abc import Iterator
class InfiniteNaturals(Iterator):
def __init__(self, start=0):
self.current = start
# 返回自身
def __iter__(self):
return self
# 真正“取下一个值”的逻辑
def __next__(self):
# 这里没有终止条件——真的是无限序列!
result = self.current
self.current += 1
return result
# 测试
nat = InfiniteNaturals(10)
print(next(nat)) # 10
print(next(nat)) # 11
# 注意:如果直接用 for 循环 nat,会进入无限循环哦!
如果你的序列有尽头,只需在 __next__() 中加上终止条件即可:
class RangeCustom(Iterator):
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration # 到达终点,主动终止
result = self.current
self.current += 1
return result
# 测试
for num in RangeCustom(2, 5):
print(num) # 依次输出 2, 3, 4
3. 迭代器是“一次性”的!
这个特性非常容易踩坑:迭代器遍历完一次就空了,再用 next() 或 for 循环都无法获得任何值。
it = iter([1, 2, 3])
print(list(it)) # [1, 2, 3]
print(list(it)) # [] —— 空的!
小贴士:如果你需要重复遍历,应该每次重新获取新的迭代器,或者直接保存原始可迭代对象(如列表),需要时再调用 iter()。
总结
彻底理解迭代器和可迭代对象,是写出高效、Pythonic、内存友好代码的重要基石。让我们用一句话再巩固一下:
- 可迭代对象是“原料/容器”,它能“提供”迭代器
- 迭代器是“工具/工人”,它握着惰性计算的逻辑,按需产出下一个值
for 循环只是迭代器模式的语法糖
- 自定义迭代器需要实现
__iter__()(返回自身)和 __next__()
下次写遍历代码时,不妨多想一步:这里用列表好,还是用生成器或迭代器更合适?你的程序可能会因此变得更加优雅和健壮。