Python 中可迭代对象和迭代器有什么区别?
简化版
可迭代对象是能被 iter() 转成迭代器的对象,通常实现 __iter__;迭代器是能被 next() 逐个取值的对象,必须实现 __iter__ 和 __next__。列表是可迭代对象但不是迭代器,生成器对象本身就是迭代器。
详细版
判断一个对象能不能用于 for 循环,本质上看它是否可迭代:
items = [1, 2, 3]
for item in items:
print(item)
for 循环背后大致会做:
it = iter(items)
while True:
try:
item = next(it)
except StopIteration:
break
区别:
| 对象 | 需要能力 | 特点 |
|---|---|---|
| 可迭代对象 | __iter__ 返回迭代器 | 可以被 for 遍历 |
| 迭代器 | __iter__ 返回自己,__next__ 返回下一个元素 | 保存遍历状态 |
示例:
lst = [1, 2, 3]
it = iter(lst)
print(next(it)) # 1
print(next(it)) # 2
列表每次调用 iter(lst) 会返回新的迭代器,所以可以重复遍历;迭代器通常会被消耗,遍历完后再遍历就没有元素了。
完整版教学
一、for 循环背后的协议
Python 的 for 循环不只服务列表,它服务所有可迭代对象:
for x in obj:
...
背后依赖迭代协议:
- 调用
iter(obj)获取迭代器; - 不断调用
next(iterator)获取下一个值; - 遇到
StopIteration结束循环。
所以只要一个对象能提供这个协议,就能放进 for。
这就是 Python 很重要的协议式编程思想:不一定要求继承某个类,只要实现约定方法即可。
二、可迭代对象是什么
可迭代对象通常实现:
__iter__()
它返回一个迭代器。
列表、元组、字符串、字典、集合都是可迭代对象:
for ch in "abc":
print(ch)
for key in {"a": 1, "b": 2}:
print(key)
它们本身保存数据,但不一定保存“当前遍历到哪里”的状态。遍历状态通常由迭代器保存。
三、迭代器是什么
迭代器必须实现:
__iter__()
__next__()
其中 __iter__ 返回自己,__next__ 返回下一个值。
自定义迭代器:
class Counter:
def __init__(self, limit):
self.current = 0
self.limit = limit
def __iter__(self):
return self
def __next__(self):
if self.current >= self.limit:
raise StopIteration
value = self.current
self.current += 1
return value
使用:
for x in Counter(3):
print(x)
输出 0、1、2。
四、为什么列表不是迭代器
列表是可迭代对象:
lst = [1, 2, 3]
iter(lst)
但它不是迭代器:
next(lst) # TypeError
原因是列表不负责保存当前遍历位置。每次调用 iter(lst),都会创建一个新的列表迭代器。
it1 = iter(lst)
it2 = iter(lst)
print(next(it1)) # 1
print(next(it2)) # 1
两个迭代器互不影响。
五、迭代器为什么通常只能遍历一次
迭代器保存自身状态,调用 next() 会推进状态。
it = iter([1, 2])
print(list(it)) # [1, 2]
print(list(it)) # []
第一次已经消耗完了,第二次没有剩余元素。
生成器对象也是迭代器,所以也有这个特性:
g = (x for x in range(3))
list(g)
list(g) # []
如果需要多次遍历,要重新创建迭代器或保存结果。
六、如何设计自定义可迭代对象
更常见的是让容器对象可迭代,而不是让它自己变成迭代器:
class NumberBox:
def __init__(self, numbers):
self.numbers = numbers
def __iter__(self):
return iter(self.numbers)
这样每次遍历都会返回新的迭代器:
box = NumberBox([1, 2, 3])
list(box)
list(box)
都能得到完整结果。
七、常见误区与追问
记忆钩子:可迭代对象负责“能不能拿到迭代器”,迭代器负责“下一步产出什么”。列表是仓库,迭代器是仓库里的取货游标。
- 误区:可迭代对象和迭代器是同一个概念。 可迭代对象实现
__iter__并返回迭代器;迭代器还要实现__next__,并记录当前遍历位置。 - 追问:为什么列表不是迭代器? 列表本身不保存某一次遍历进度,每次
iter(list)会产生新的列表迭代器;这让同一个列表可以被多次独立遍历。 - 误区:迭代器可以随便重复遍历。 迭代器通常是一次性游标,消耗完后继续迭代会抛
StopIteration;要重复遍历应重新创建迭代器或保存结果。 - 追问:
for循环背后发生了什么? 先调用iter(obj)获取迭代器,再反复调用next(iterator),遇到StopIteration正常结束循环。 - 误区:实现
__getitem__就一定是标准迭代器。 旧式序列协议可让对象被迭代,但现代自定义对象更推荐明确实现__iter__,语义更清楚。 - 追问:生成器对象属于哪一类? 生成器对象本身就是迭代器,既可
iter(g)返回自身,也可next(g)逐步产出值。
八、加强记忆
可迭代对象回答“我能不能被遍历”,迭代器回答“我当前遍历到哪里了”。iter() 拿迭代器,next() 取下一个值,StopIteration 表示结束。列表可迭代但不是迭代器,生成器对象本身就是迭代器,这是面试最常考的对比。