跳到主要内容

迭代器

迭代器(iterator)是一个能够记住当前位置,并在每次调用 next() 时提供下一个值的对象。for 循环、生成器、map() 和 zip() 都建立在迭代器协议之上。

理解迭代器时,要区分两个概念:

  • 可迭代对象:能够通过 iter() 提供迭代器,例如列表、字符串和字典;
  • 迭代器:实现了 __iter__() 和 __next__(),负责逐项产生值。

一个列表可以创建多个独立迭代器;一个迭代器本身通常只能顺序前进并被消费一次。

iter() 和 next()​

values = [10, 20, 30]
iterator = iter(values)

print(next(iterator)) # 10
print(next(iterator)) # 20
print(next(iterator)) # 30
# next(iterator) # StopIteration

iter(values) 不会复制列表,而是返回一个记录当前位置的列表迭代器。next() 取值时,迭代器向前移动;当没有更多值时抛出 StopIteration。

可以用第二个参数给 next() 提供默认值,避免手动捕获异常:

iterator = iter([1, 2])
print(next(iterator, None)) # 1
print(next(iterator, None)) # 2
print(next(iterator, None)) # None

注意:默认值只在 next() 调用处生效,不能让迭代器重新开始。

迭代器协议​

一个对象要成为迭代器,通常需要实现:

class Iterator:
def __iter__(self):
return self

def __next__(self):
# 返回下一个值;结束时 raise StopIteration
...

__iter__() 应返回一个迭代器。迭代器通常返回自己,因为它已经保存了当前状态;__next__() 每次返回一个值,耗尽时必须抛出 StopIteration,不能返回 None 伪装成结束。

自定义一个倒计时迭代器​

class Countdown:
def __init__(self, start):
self.current = start

def __iter__(self):
return self

def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value


for number in Countdown(3):
print(number) # 3、2、1

for 会自动调用这两个方法,因此使用者不需要了解内部状态。这个对象是“可迭代对象”和“迭代器”合一的例子。

可迭代对象和迭代器的区别​

列表是可迭代对象,但不是迭代器:

values = [1, 2, 3]
# next(values) # TypeError

first = iter(values)
second = iter(values)
print(first is second) # False,每次得到独立迭代器

迭代器自己也是可迭代对象:

iterator = iter(values)
print(iter(iterator) is iterator) # True

这也是为什么同一个迭代器不能安全地被两个循环同时从头遍历。若一个对象需要“每次 for 都重新开始”,应让它的 __iter__() 每次返回新的迭代器,而不是在对象内部只保存一个已消费的迭代器。

把可调用对象转换成迭代器​

iter(callable, sentinel) 会反复调用一个无参数函数,直到返回哨兵值(sentinel),但不会把哨兵值产出:

from io import StringIO

stream = StringIO("first\nsecond\n")
for line in iter(stream.readline, ""):
print(line.rstrip())

文件到达末尾时 readline() 返回空字符串,循环自动结束。这种写法比手动 while True 加 break 更集中地表达“读取直到结束”。

哨兵值应当与正常返回值明确区分:

value = iter(lambda: input("输入数字:"), "quit")

上例会持续读取,直到用户输入字符串 quit。不要把可能是正常数据的值当作哨兵。

迭代器的常见来源​

标准库中许多工具都返回迭代器:

from itertools import count, islice

mapped = map(str, [1, 2, 3])
filtered = filter(lambda value: value > 1, [1, 2, 3])
paired = zip([1, 2], ["a", "b"])

print(list(mapped)) # ['1', '2', '3']
print(list(filtered)) # [2, 3]
print(list(paired)) # [(1, 'a'), (2, 'b')]

infinite = count(10)
print(list(islice(infinite, 3))) # [10, 11, 12]

itertools 提供了大量惰性工具,例如 chain()、islice()、takewhile()、dropwhile()、groupby() 和 zip_longest()。处理大型或无限数据时,应先确认消费边界,避免把无限迭代器直接传给 list()。

迭代器耗尽后会怎样​

迭代器耗尽后应持续抛出 StopIteration,而不是恢复或返回新数据:

iterator = iter([1])
print(next(iterator))
print(next(iterator, "结束")) # 结束
print(next(iterator, "结束")) # 结束

如果自定义迭代器在耗尽后又产生值,调用者可能得到违反直觉的结果,也不符合常见迭代器约定。需要重新开始时,创建一个新的迭代器对象。

迭代器与生成器的关系​

生成器是 Python 自动实现迭代器协议的一种方式:

def values():
yield 1


generator = values()
print(iter(generator) is generator) # True
print(next(generator)) # 1

手写类适合需要清晰表达状态、提供额外方法或复用复杂状态机的场景;生成器适合顺序逻辑和数据管道。二者都遵守 __iter__() / __next__() 协议。

迭代器的状态和并发​

迭代器通常包含可变状态,例如当前位置、缓存或打开的资源。不要在多个线程或协程中无保护地共享同一个迭代器,否则可能发生值重复、跳过或顺序混乱。需要并发消费时,应使用队列、锁或为每个消费者创建独立迭代器。

修改底层容器也可能使结果难以预测:

values = [1, 2, 3]
iterator = iter(values)
values.append(4)
print(list(iterator)) # 内置列表迭代器可能看到后追加的元素

不同容器的行为可能不同,不要把“迭代时修改容器”作为业务逻辑依赖。需要稳定快照时,先复制数据。

迭代器的性能取舍​

迭代器一般具有 O(1) 的额外状态空间,可以流式处理大量数据;但随机访问、倒序访问和计算长度并不是它们的职责:

iterator = iter(range(100))
# iterator[3] # TypeError

如果确实需要索引或多次遍历,可以在边界处转换为列表:

values = list(iterator)
print(values[3])

这会一次性消耗迭代器并分配内存,应根据数据规模和后续操作选择时机。

类型提示(可选)​

在类型标注中,可以用 collections.abc.Iterable 表示“可被迭代”,用 Iterator 表示“可调用 next() 且会被消费”的对象:

from collections.abc import Iterable, Iterator


def double(values: Iterable[int]) -> Iterator[int]:
for value in values:
yield value * 2

把参数标注为 Iterable,通常比标注为 list 更灵活;调用方可以传入元组、生成器或文件对象。若函数会多次遍历参数,则应在接口说明中明确要求可重复迭代的数据,或在内部物化结果。

常见错误​

__next__() 返回 None 表示结束​

None 可能是合法数据,迭代结束必须抛出 StopIteration:

def __next__(self):
if self.finished:
raise StopIteration

__iter__() 返回列表或其他非迭代器​

__iter__() 应返回迭代器。如果只返回一个列表,某些调用场景会得到不符合协议的对象;可以返回 iter(self.values) 或使用生成器。

重复使用已经耗尽的迭代器​

把 map()、zip()、文件迭代器或生成器传给第一个函数后,它们可能已经没有数据。需要复用时重新创建或显式转换为列表。

无边界地消费无限迭代器​

itertools.count() 等对象不会自然结束。使用 islice()、条件判断或其他明确边界限制消费数量。

练习​

  1. 实现一个 RangeIterator,支持从 start 迭代到 stop 之前的整数。
  2. 写一个迭代器,把字符串中的字符逐个返回,并在结束后持续抛出 StopIteration。
  3. 使用 iter(callable, sentinel) 读取多行文本,直到返回空字符串。
  4. 对比列表、列表迭代器和生成器的 iter() 返回值与 next() 行为。
  5. 使用 itertools.islice() 只取无限计数器的第 100 到 109 项。

小结​

  • 迭代器通过 __iter__() 和 __next__() 协议逐项提供数据。
  • iter() 从可迭代对象取得迭代器,next() 取得下一个值;耗尽时抛出 StopIteration。
  • 可迭代对象通常可创建多个迭代器,迭代器本身通常只能顺序消费一次。
  • iter(callable, sentinel) 适合反复读取直到遇到结束标记。
  • 生成器、map()、filter()、zip() 和 itertools 工具都是常见迭代器来源。
  • 迭代器节省内存但不提供随机访问;需要复用或索引时再物化为列表。