(十一)可迭代对象与迭代器
1. 从 for 展开到 iter 与 next
第二章已经看到:一个 map 对象读完后,再读只剩空结果。列表则能重新从第一项开始。区别在于:列表保存数据,迭代器还保存当前读到的位置。
# iter 从列表取得迭代器;next 让这个迭代器向前读取一项。
values = [20, 35]
reader = iter(values)
print(next(reader))
print(next(reader))
# 给 next 提供默认值后,读完时返回默认值,而不是继续索引列表。
print(next(reader, "读完了"))
print(list(reader))
# 对列表再次调用 iter,取得一份从头开始的读取位置。
print(list(iter(values)))输出依次是 20、35、读完了、[]、[20, 35]。可迭代对象能通过 iter(...) 取得迭代器;迭代器响应 next(...),依次提供值,并记录自己的进度。迭代器本身也是可迭代对象,对它调用 iter(reader) 就是取得它自身,进度不会重置。
标准迭代器通过 __iter__ 返回自身,通过 __next__ 提供下一项;读完时以 StopIteration 表示结束。这些双下划线方法组成迭代协议。for 会处理正常的结束信号,因此不用在每一轮自己写异常处理;自定义这些方法留到面向对象章节。
可以把 for item in values 理解为“先取一个迭代器,再不断取下一项,读完停止”。这是动作上的展开;手动写 next(reader) 而不给默认值,读完后会看到 StopIteration 报错。
2. 只遍历一次的统计函数
先后写 sum(values) 与 len(values),隐含着数据支持长度查询。若收到的是 map,它没有 len,而且求和已经把迭代器读完。可以在同一轮里计算总量与条数:
# Iterable[int] 描述可逐项读取的数据,不要求它预先保存成列表。
from collections.abc import Iterable
def total_and_count(values: Iterable[int]) -> tuple[int, int]:
total = 0
count = 0
for value in values:
total += value
count += 1
return total, count
reader = map(int, ["20", "35"])
print(total_and_count(reader))
print(list(reader))
print(total_and_count([10, 15, 30]))得到 (55, 2)、[]、(55, 3)。统计函数消费了传进来的读取位置。需要重新统计时,对保存的数据重新建立迭代器,或者重新建立数据来源。这个版本对 n 项数据遍历一次,工作量为 ,函数额外保存的统计状态为 。内置 iter 与 next