直接用 range(len(...)) 获取索引是错觉上的“简单”,因其可读性差、易越界、不支持生成器;应优先使用 enumerate(),它安全通用、内存友好、支持 start 参数,且需正确解包如 for idx, val in enumerate(items)。
为什么直接用 range(len(...)) 获取索引是错觉上的“简单”
很多人写 for i in range(len(items)): 然后手动取 items[i],看似能拿到索引和值,但实际隐藏三个问题:一是可读性差,语义不明确;二是容易越界(比如误写成 i+1);三是对生成器、迭代器(如 map()、文件对象)根本不可用——它们不支持 len() 或随机索引。
真正安全、通用、符合 Python 习惯的做法,是用 enumerate()。
-
enumerate()返回的是一个迭代器,每个元素是(index, value)元组,天然解包友好 - 它不强制求值整个序列,内存友好,适用于大列表甚至无限迭代器(只要你不真跑完)
- 支持
start参数,比如从 1 开始编号:enumerate(items, start=1)
enumerate() 解包时常见的语法错误
最常出错的是忘记括号或写错结构,导致 ValueError: too many values to unpack。
正确写法必须是元组解包形式:
for idx, val in enumerate(items):
print(idx, val)
错误写法举例:
包阅AI
论文对照翻译,改写润色,专业术语详解,选题评估,开题报告分析,评审校对,一站式解决论文烦恼!
-
for idx, val in enumerate(items):✅ 正确 -
for idx, val in enumerate(items):❌ 如果items是字符串,val是字符,没问题;但如果items是二维列表而你没意识到,就会意外解包失败 -
for (idx, val) in enumerate(items):✅ 括号可选,但加了更清晰 -
for idx in enumerate(items):❌ 这样idx是整个元组,不是数字,后续用idx + 1会报TypeError
当 items 是字典时,enumerate() 迭代的是什么
字典本身是无序(Python 3.7+ 保持插入序)的键值容器,但 enumerate(dict) 并不遍历键值对,而是遍历它的 键(等价于 enumerate(dict.keys()))。
如果你想要索引 + 键 + 值,得显式展开:
d = {'a': 10, 'b': 20}
for i, (k, v) in enumerate(d.items()):
print(i, k, v)
# 输出:
# 0 a 10
# 1 b 20
- 注意
d.items()返回的是(key, value)元组,所以外层enumerate()包一层,内层再用(k, v)解包 - 不能写成
for i, k, v in enumerate(d.items())—— 因为enumerate()每次产出两个元素:(i, (k, v)),不是三个 - 如果只想要索引和键,用
for i, k in enumerate(d)就够了
性能差异真的存在吗?什么时候该犹豫用 enumerate()
在绝大多数场景下,enumerate() 和手写 range(len(...)) 性能几乎一致,C 实现的 enumerate 迭代器开销极小。但有两个真实例外:
- 当你要频繁访问
items[i]多次(比如在循环体内查 3 次),而items是 list,那直接用索引反而少一次解包、少一次 tuple 创建,略快一点点(微秒级,别过早优化) - 当你需要反向索引(比如从末尾往前数),
enumerate(reversed(items))不如for i in range(len(items)-1, -1, -1):直观,且reversed()对某些类型(如生成器)不支持
真正该警惕的不是性能,而是「想当然认为 enumerate() 能自动适配所有数据结构」——它只保证按迭代顺序编号,不改变原对象的迭代行为。比如对集合 set,顺序不确定;对自定义类,取决于它是否实现了 __iter__ 且返回合理迭代器。
就爱读