本篇整理 Python 中可迭代对象(Iterable)与迭代器(Iterator)的关系,以及 for ... in ... 背后的协议。
Python 的 for ... in ... 迭代先通过 iter() 交出迭代器(iterator),再由 next() 不断取下一个值,直到 StopIteration 结束。
1. 迭代协议
1.1 可迭代对象与迭代器
可迭代对象的英文原文是 Iterable,表示对象可以交出迭代器。它的核心接口是 __iter__(),该方法应该返回一个 Iterator。
class Iterable(metaclass=ABCMeta): @abstractmethod def __iter__(self): while False: yield None 迭代器的英文原文是 Iterator,表示对象本身就是一个数据流。它同时实现两个接口:
class Iterator(Iterable): @abstractmethod def __next__(self): 'Return the next item from the iterator. When exhausted, raise StopIteration' raise StopIteration def __iter__(self): return self| 接口 | 作用 |
|---|---|
__iter__() | 返回Iterator实例,在Iterator中返回自身,使Iterator也能作为Iterable使用 |
__next__() | 返回下一个元素;没有元素时抛出 StopIteration |
Iterator 一定是 Iterable,因为它能返回自身作为迭代入口;Iterable 不一定是 Iterator,因为它可能只负责创建一个新的
iterator。
from collections.abc import Iterable, Iteratordata = [1, 2, 3] # Python 中的 list 是 Iterablestream = iter(data) # 使用 __iter__() 将 Iterable 转化为 Iteratorprint(isinstance(data, Iterable)) # Trueprint(isinstance(data, Iterator)) # Falseprint(isinstance(stream, Iterable)) # Trueprint(isinstance(stream, Iterator)) # True补充
Iterable/Iterator是协议类,也就是说
- 只要实现了
__iter__方法,便是一个Iterable- 只要实现了
__iter__方法与__next__方法,便是一个Iterator例如,如下代码中,第一个
True,但第二个False,即使B类型并没有继承自Iterable。pythonfrom collections.abc import Iterableclass A: num: int = 0class B: num: int = 0 def __iter__(self): passprint(isinstance(B(), Iterable)) # Trueprint(isinstance(B(), A)) # False
1.2 迭代入口与取值函数
迭代入口函数是 iter(),取值函数是 next()。iter(iterable) 向 iterable 请求 iterator,实际上调用对象的__iter__()方法;
next(iterator) 向 iterator 请求下一个值,实际上调用对象的
__next__()方法。
it = iter(['A', 'B'])print(next(it)) # Aprint(next(it)) # B当 iterator 耗尽时,next() 会抛出 StopIteration:
it = iter([1])print(next(it)) # 1try: next(it)except StopIteration: print('empty') # emptyIterator 是一次性对象。被消费过的元素不会重新出现;需要重新遍历时要重新调用 iter() 或重新创建 iterator。
1.3 循环语句的执行模型
这里的循环语句指 for ... in ...。它会先把右侧对象转换为 iterator,然后不断调用 next() 直到尾部:
for value in [1, 2, 3]: print(value) # 依次输出 1、2、3等价于
it = iter([1, 2, 3]) # 转化为 Iteratorwhile True: try: value = next(it) # 不断调用 next() except StopIteration: break print(value) # 依次输出 1、2、3因此,for 循环只要求它是一个Iterable,能通过 iter() 交出一个 iterator,然后使用 next() 遍历直到尾部。
2. 内建可迭代对象
2.1 内建容器类型
这里以内建容器类型 list、tuple、str、dict 为例。常见内建容器都是 Iterable:
from collections.abc import Iterableprint(isinstance([1, 2], Iterable)) # Trueprint(isinstance((1, 2), Iterable)) # Trueprint(isinstance('AB', Iterable)) # Trueprint(isinstance({'a': 1}, Iterable)) # Trueprint(isinstance(123, Iterable)) # False但不一定是Iterator:
from collections.abc import Iteratorprint(isinstance([1, 2], Iterator)) # Falseprint(isinstance((1, 2), Iterator)) # Falseprint(isinstance('AB', Iterator)) # Falseprint(isinstance({'a': 1}, Iterator)) # Falseprint(isinstance(123, Iterator)) # Falsestr 迭代字符:
for ch in 'ABC': print(ch) # 依次输出 A、B、Cdict 默认迭代 key;要迭代 value 或 key-value 对,需要显式使用 .values() 或 .items()。
d = {'a': 1, 'b': 2}for key in d: print(key) # 依次输出 a、bfor key, value in d.items(): print(key, value) # 依次输出 a 1、b 22.2 解包
多个变量的迭代依赖解包,元素结构要与变量个数匹配:
pairs = [(1, 1), (2, 4), (3, 9)]for x, y in pairs: print(x, y) # 依次输出 1 1、2 4、3 9dict.items() 返回的就是可迭代的二元组序列,因此可以直接写成 for key, value in ...。
items = {'a': 1, 'b': 2}.items()for key, value in items: print(key, value) # 依次输出 a 1、b 2解包不是 for 循环的专属语法,普通赋值也可以使用:
x, y = (10, 20)print(x, y) # 10 202.3 索引枚举
for ... in ... 本身不返回下标,只返回 next() 方法产出的值:
for value in ['A', 'B', 'C']: print(value) # 依次输出 A、B、C需要下标时使用索引枚举函数 enumerate():
for index, value in enumerate(['A', 'B', 'C']): print(index, value) # 依次输出 0 A、1 B、2 Cenumerate(iterable) 会返回一个 iterator ,next() 每次产出 tuple[int, T]。for index, value in ... 再把这个二元组解包成
index 与 value。
from collections.abc import Iterable, Iteratorfrom typing import TypeVarT = TypeVar('T')class Enumerate(Iterator[tuple[int, T]]): def __init__(self, iterable: Iterable[T], start: int = 0): self.index = start self.iterator = iter(iterable) def __next__(self) -> tuple[int, T]: value = next(self.iterator) current = self.index self.index += 1 return current, valueprint(list(Enumerate(['A', 'B']))) # [(0, 'A'), (1, 'B')]3. 序列转换工具
3.1 map/filter
map(func, iterable) 把函数应用到每个元素,返回惰性 iterator。
def square(x: int) -> int: return x * xmapped = map(square, [1, 2, 3])print(type(mapped).__name__) # mapprint(list(mapped)) # [1, 4, 9]filter(func, iterable) 根据谓词函数的真假值保留元素,也返回惰性 iterator。
def is_odd(x: int) -> bool: return x % 2 == 1filtered = filter(is_odd, [1, 2, 3, 4, 5])print(list(filtered)) # [1, 3, 5]清理空字符串时,谓词函数返回 truthy/falsy 值即可:
def not_empty(value: str | None) -> bool: return bool(value and value.strip())items = filter(not_empty, ['A', '', 'B', None, ' ', 'C'])print(list(items)) # ['A', 'B', 'C']map 和 filter 的结果都是 iterator,被 list() 消费一次后就没有剩余元素。
def is_even(x: int) -> bool: return x % 2 == 0filtered = filter(is_even, [1, 2, 3, 4, 5])print(list(filtered)) # [2, 4]print(next(filtered)) # 抛出 StopIteration 异常3.2 reduce
reduce(func, iterable) 把二元函数连续应用到累计值和下一个元素上,最终得到单个结果。
from functools import reducedef combine(acc: int, next: int) -> int: return acc * 10 + nextprint(reduce(combine, [1, 3, 5, 7, 9])) # 13579reduce 适合表达累计折叠。普通求和直接用 sum() 更清楚;需要自定义累计状态时再考虑 reduce。
from functools import reducedigits = {'0': 0, '1': 1, '2': 2, '3': 3}def char_to_num(ch: str) -> int: return digits[ch]def str_to_int(text: str) -> int: return reduce(lambda x, y: x * 10 + y, map(char_to_num, text))print(str_to_int('123')) # 1233.3 sort
sorted(iterable) 返回新 list(而不是iterator),不修改原对象。key 方法负责把每个元素映射为排序依据。
nums = [36, 5, -12, 9, -21]print(sorted(nums)) # [-21, -12, 5, 9, 36]print(sorted(nums, key=abs)) # [5, 9, -12, -21, 36]print(nums) # [36, 5, -12, 9, -21]字符串默认按码点排序,大小写会影响结果。忽略大小写时传入 str.lower:
names = ['bob', 'about', 'Zoo', 'Credit']print(sorted(names)) # ['Credit', 'Zoo', 'about', 'bob']print(sorted(names, key=str.lower)) # ['about', 'bob', 'Credit', 'Zoo']print(sorted(names, key=str.lower, reverse=True)) # ['Zoo', 'Credit', 'bob', 'about']对结构化数据排序时,key 应直接指向排序字段:
students = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]print(sorted(students, key=lambda item: item[0])) # [('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]print(sorted(students, key=lambda item: item[1], reverse=True)) # [('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]4. 自定义可迭代对象
4.1 列表推导式
列表推导式的英文原文是 list comprehension。它把“从一个 iterable 映射出一个 list”的模式压缩成表达式,类似于 filter 与
map 的组合。
squares = [x * x for x in range(1, 6)]print(squares) # [1, 4, 9, 16, 25]for 后面的 if 是过滤条件:
even_squares = [x * x for x in range(1, 11) if x % 2 == 0]print(even_squares) # [4, 16, 36, 64, 100]for 前面的 if ... else ... 是映射表达式,决定每个输入元素映射成什么:
items = [x if x % 2 == 0 else -x for x in range(1, 6)]print(items) # [-1, 2, -3, 4, -5]多层循环按从左到右的嵌套顺序展开:
items = [m + n for m in 'AB' for n in 'XY']print(items) # ['AX', 'AY', 'BX', 'BY']4.2 生成器表达式
生成器表达式的英文原文是 generator expression。把列表推导式的 [] 换成 (),得到的就是生成器表达式。它不立即构造完整
list,而是生成一个Iterator对象,按需获取。
from collections.abc import Iterator, Iterablesquares = (x * x for x in range(5)) # IteratorsquaresList = [x * x for x in range(5)] # listprint(isinstance(squares, Iterator)) # Trueprint(isinstance(squaresList, Iterator)) # Falseprint(isinstance(squaresList, Iterable)) # Trueprint(next(squares)) # 0print(next(squares)) # 1print(list(squares)) # [4, 9, 16]因为返回的是Iterator对象而非Iterable对象,所以 generator expression 是一次性数据流。已经取出的元素不会重新出现;需要重新遍历时要重新创建。
4.3 yield与生成器函数
生成器函数的英文原文是 generator function。函数体里出现 yield,调用函数时不会整个执行函数体,而是创建一个 Iterator 对象。每次
next() 让函数运行到下一个 yield,并在该位置暂停。
def odd(): print('step 1') # 第一次 next 时输出 yield 1 print('step 2') # 第二次 next 时输出 yield 3 print('step 3') # 第三次 next 时输出 yield 5g = odd() # Iterator对象print(isinstance(g, Iterable)) # Trueprint(isinstance(g, Iterator)) # Trueprint(next(g)) # 先输出 step 1,再输出 1print(next(g)) # 先输出 step 2,再输出 3print(next(g)) # 先输出 step 3,再输出 5yield 保存函数内部状态,下次继续从暂停点向后执行。函数执行到末尾或遇到 return 后,后续 next() 会触发 StopIteration。
g = odd()while True: try: print(next(g)) # 依次输出 1、3、5 except StopIteration: print('done') # generator 耗尽后输出 break普通 for 循环会自动处理 StopIteration,因此大多数场景不手动写上面的循环。
4.4 自定义迭代器
直接写迭代器(Iterator)类时,__iter__() 返回自身,__next__() 推进内部状态。
from collections.abc import Iteratorclass Countdown: def __init__(self, start: int): self.current = start def __iter__(self): return self def __next__(self): if self.current <= 0: raise StopIteration value = self.current self.current -= 1 return valuecounter = Countdown(3)print(isinstance(counter, Iterable)) # Trueprint(isinstance(counter, Iterator)) # Trueprint(list(counter)) # [3, 2, 1]print(list(counter)) # []最后一行为空 list,因为 counter 本身就是 iterator,第一次 list(counter) 已经把它推进到末尾。
只需要“可重复遍历”的对象时,让 __iter__() 每次返回新的 iterator:
class CountdownRange: def __init__(self, start: int): self.start = start def __iter__(self): return iter(range(self.start, 0, -1))numbers = CountdownRange(3)print(isinstance(numbers, Iterable)) # Trueprint(isinstance(numbers, Iterator)) # Falseprint(list(numbers)) # [3, 2, 1]print(list(numbers)) # [3, 2, 1]4.5 状态保持
生成器适合表达“当前状态可以推导出下一项”的序列。斐波那契数列只需要保存两个相邻值:
def fib(limit: int): a, b = 0, 1 for _ in range(limit): a, b = b, a + b yield aprint(list(fib(6))) # [1, 1, 2, 3, 5, 8]杨辉三角每一行由上一行推导得到,也适合写成生成器:
def triangles(): row = [1] while True: yield row row = [1] + [row[i] + row[i + 1] for i in range(len(row) - 1)] + [1]g = triangles()print(next(g)) # [1]print(next(g)) # [1, 1]print(next(g)) # [1, 2, 1]这里每次都把 row 赋值为新 list,所以已经产出的行不会被后续计算原地改写。若复用同一个 list 并原地修改,就要小心外部保存的引用也会变化。
5. 小结
本篇主线是 Python 的迭代协议:Iterable 通过 __iter__() 交出 iterator,Iterator 通过 __next__() 按需产出值并用
StopIteration 表示结束。
内建容器提供了可迭代对象的常见实例;enumerate()、推导式、map/filter/reduce/sorted 都围绕 Iterable 做转换;generator
expression、generator function 和自定义类则是构造 Iterable 或 Iterable 的常用方式。