文章
合集Python 语言基础第 7 / 21 篇

for-in 循环原理与可迭代对象

一、术语定义

术语定义关键标识
可迭代对象(Iterable)实现 __iter__() 方法的对象,或实现了 __getitem__() 的序列能被 for 循环遍历,能被 iter() 函数转换
迭代器(Iterator)同时实现 __iter__() 和 __next__() 的对象有内部状态,记录当前遍历位置
惰性求值(Lazy Evaluation)元素在被请求时才计算或产出,而非预先计算并存储不预存完整结果,遍历即计算
一次性迭代器(Exhaustible Iterator)遍历结束后即耗尽,无法重置或重复遍历再次遍历返回空,不支持 len() 和索引
序列(Sequence)实现 __getitem__() 和 __len__() 的可重复访问集合list、tuple、str、range
动态视图(Dynamic View)反映底层数据结构实时状态的轻量级代理对象随原数据变化,数据已存在而非惰性计算

二、for-in 循环的机制

for x in iterable: 语句并非简单的遍历语法糖,其底层严格遵循迭代器协议:

for x in iterable:
    process(x)

# 等价于以下显式逻辑
_iterator = iter(iterable)      # 调用 iterable.__iter__(),获取迭代器
try:
    while True:
        x = next(_iterator)     # 调用 _iterator.__next__()
        process(x)
except StopIteration:           # 无更多元素时抛出,循环终止
    pass

核心要点:

  • iter(iterable) 负责将任何可迭代对象转换为迭代器;若对象本身已是迭代器,则返回自身。
  • next(iterator) 驱动迭代器前进一步,返回当前元素;耗尽时抛出 StopIteration。
  • for 循环的本质是迭代器协议,而非索引访问。

三、可迭代对象与迭代器的关系

3.1 类型层级

Iterable(可迭代对象)
│
├── Iterator(迭代器)
│   ├── map 对象
│   ├── filter 对象
│   ├── zip 对象
│   ├── enumerate 对象
│   ├── 生成器(generator)
│   └── itertools 各类对象
│
├── Sequence(序列)
│   ├── list / tuple / str(饥渴序列,数据已存储)
│   └── range(惰性序列,可重复遍历)
│
└── MappingView(映射视图)
    ├── dict_keys
    ├── dict_values
    └── dict_items(动态视图,非惰性)

3.2 Iterable 与 Iterator 的区别

特性IterableIterator
方法要求__iter__()__iter__() + __next__()
遍历状态无状态,每次调用 iter() 产生新迭代器有状态,记录当前位置
重复遍历支持通常不支持(一次性)
next() 调用不可直接调用可直接调用

关键关系:iter(iterable) 返回一个独立的 Iterator 对象,该对象负责实际遍历。

四、以可迭代对象为参数的函数

Python 中大量内置函数和标准库函数接受 Iterable 作为参数,返回的是一次性惰性迭代器,而非存储型序列。

4.1 共同特征

  • 接受任何可迭代对象(list、tuple、set、dict、文件对象、生成器等)。
  • 返回惰性迭代器对象,元素在被请求时才计算。
  • 遍历结束后即耗尽,不可重复遍历,不支持 len() 和索引访问。
  • 不存储完整结果集,空间复杂度为 O(1)。

4.2 内置惰性转换函数

函数返回类型作用示例
map(func, iterable)map 对象对可迭代对象每个元素应用函数map(str, [1, 2])
filter(func, iterable)filter 对象保留满足条件的元素filter(bool, [0, 1])
zip(*iterables)zip 对象并行打包多个可迭代对象zip([1,2], ['a','b'])
enumerate(iterable, start=0)enumerate 对象带索引枚举enumerate('abc')
reversed(sequence)list_reverseiterator反向迭代reversed([1, 2, 3])

示例:

m = map(str, [1, 2, 3])
print(type(m))          # <class 'map'>
print(list(m))          # ['1', '2', '3']
print(list(m))          # [] —— 已耗尽,第二次为空

4.3 itertools 模块

itertools 是标准库中处理惰性迭代的模块,全部返回迭代器对象。

函数作用
itertools.chain(*iterables)串联多个可迭代对象
itertools.chain.from_iterable(iterable)扁平化一层嵌套
itertools.islice(iterable, start, stop, step)惰性切片
itertools.compress(data, selectors)按条件选择元素
itertools.dropwhile(pred, iterable)跳过满足条件的开头元素
itertools.takewhile(pred, iterable)取满足条件的开头元素
itertools.filterfalse(pred, iterable)filter 的反操作
itertools.groupby(iterable, key=None)按 key 分组(需先排序)
itertools.tee(iterable, n=2)复制迭代器为 n 个独立迭代器
itertools.zip_longest(*iterables, fillvalue=None)不等长 zip,填充默认值
itertools.count(start=0, step=1)无限计数器
itertools.cycle(iterable)无限循环可迭代对象
itertools.repeat(elem, n)重复元素 n 次
itertools.permutations(iterable, r)排列
itertools.combinations(iterable, r)组合
itertools.product(*iterables)笛卡尔积

4.4 生成器表达式

生成器表达式是用类推导式语法创建的惰性迭代器:

# 列表推导式 —— 饥渴计算,立即存储
[x**2 for x in range(1000000)]

# 生成器表达式 —— 惰性计算,逐个产出
(x**2 for x in range(1000000))

特征:

  • 语法为 (表达式 for 变量 in 可迭代对象 if 条件)。
  • 单参数函数调用时外层括号可省略:sum(x**2 for x in range(10))。
  • 只能遍历一次,不支持索引,不支持 len()。

五、一次性迭代器与可重复遍历对象的区别

维度一次性惰性迭代器(如 map、filter、zip)饥渴序列(如 list、tuple、str)惰性序列(range)
数据存储不存储元素,只存计算规则与当前位置预先存储所有元素不存储,但记住 start/stop/step
计算时机遍历时实时计算创建时或添加时已计算索引访问时按需计算
重复遍历不支持,耗尽后为空支持支持
len() 支持不支持支持支持
索引访问不支持支持支持
内存占用O(1)O(n)O(1)

5.1 特殊案例:range

range 是 Python 中唯一的可重复使用的惰性序列:

r = range(1000000000000)    # 瞬间创建,不占内存
len(r)                      # 支持:1000000000000
r[999999]                   # 支持:999999
for i in r: print(i)        # 第一次遍历
for i in r: print(i)        # 第二次仍可遍历(与 map 等不同)

range 记住的是计算规则(start、stop、step),可随时重新计算任意位置的值,因此兼具惰性与可重复遍历的特性。

5.2 动态视图对象(非惰性)

dict_keys、dict_values、dict_items 属于动态视图对象,而非惰性对象:

d = {'a': 1, 'b': 2}
keys = d.keys()
d['c'] = 3
print(list(keys))           # ['a', 'b', 'c'] —— 实时反映原字典变化
print(len(keys))            # 支持 len()
print(list(keys))           # 可重复遍历

关键区别:

  • 惰性对象:数据尚未产生,遍历时才计算(如 map、filter)。
  • 动态视图:数据已存在于字典中,视图仅提供实时访问窗口,不延迟计算。

六、生成器函数、生成器对象与迭代器的区分

日常口语中常将以下三者混用,需严格区分:

概念类型说明
生成器函数(Generator Function)函数包含 yield 语句的函数,type 为 function
生成器对象(Generator)对象调用生成器函数后返回的对象,type 为 generator
迭代器(Iterator)协议/对象实现 __next__() 和 __iter__() 的对象

关系:

  • 生成器函数是函数,调用后不执行函数体,而是返回一个生成器对象。
  • 生成器对象是迭代器的子类,具备一次性、惰性、不支持 len() 和索引等全部迭代器特征。
  • 生成器表达式创建的直接就是生成器对象,无需函数定义。
def my_gen():               # 生成器函数(function)
    yield 1
    yield 2

g = my_gen()                # 调用后得到生成器对象(generator)
print(type(g))              # <class 'generator'>

from collections.abc import Iterator
print(isinstance(g, Iterator))  # True

七、设计思路与应用场景

7.1 为什么设计为一次性惰性迭代器

  • 内存效率:处理大数据集时避免创建中间列表,全程 O(1) 内存。
  • 流式处理:构建数据处理流水线,数据像水流过管道,不落地存储。
  • 无限序列:支持 itertools.count() 等理论上无限的序列,按需取用。

7.2 流水线模式示例

# 多级惰性处理,无中间存储
lines = open('huge_file.txt')
filtered = (line.strip() for line in lines if 'error' in line)
processed = map(parse_log, filtered)

for record in processed:    # 逐行处理,内存始终只有当前元素
    save(record)

7.3 判断口诀

  • 若对象类型为 map、filter、zip、enumerate、generator,或来自 itertools,属于一次性惰性迭代器,只能遍历一次,不支持 len() 和索引。
  • 若对象支持 len()、可反复 for 循环、支持索引访问,属于可重复序列(或动态视图),非此类函数返回。

八、完整函数速查

8.1 内置可迭代工具函数

类别函数返回值类型是否惰性是否一次性
聚合sum(iterable)数值——
聚合min(iterable) / max(iterable)元素——
聚合all(iterable) / any(iterable)bool——
聚合len(iterable)int——
转换list(iterable) / tuple(iterable)序列饥渴可重复
转换set(iterable) / frozenset(iterable)集合饥渴可重复
转换dict(iterable)字典饥渴可重复
转换str.join(iterable)字符串饥渴可重复
遍历enumerate(iterable)enumerate 对象惰性一次性
遍历zip(*iterables)zip 对象惰性一次性
遍历map(func, iterable)map 对象惰性一次性
遍历filter(func, iterable)filter 对象惰性一次性
遍历reversed(sequence)list_reverseiterator惰性一次性
遍历sorted(iterable)list饥渴可重复
其他iter(iterable)迭代器—取决于输入
其他next(iterator)元素——
其他range(...)range 对象惰性可重复

8.2 对象特征速查

对象类型惰性可重复遍历支持 len()支持索引类型归属
list / tuple / str否是是是饥渴序列
range是是是是惰性序列
dict_keys / dict_values / dict_items否是是否动态视图
map / filter / zip / enumerate是否否否一次性迭代器
生成器表达式 / 生成器函数返回值是否否否一次性迭代器
itertools 各类对象是否(多数)否(多数)否一次性迭代器