← 返回题目列表

Python 的 enumerate 和 zip 怎么用?为什么不要用 range(len(x)) 遍历?

高频 简单 第 1 / 22 题 更新于 2026/07/31
Pythonenumeratezip遍历

简化版

**enumerate(iterable) 在遍历时同时给出「索引和元素」,zip(a, b, ...) 把多个可迭代对象「按位置配对」一起遍历——它们是 Python 遍历的两大惯用工具,能替代笨拙的 range(len(x)) 索引遍历。**① enumerate——for i, x in enumerate(seq) 直接拿到下标 i 和值 x,还能指定起始值 enumerate(seq, start=1)(从 1 开始编号);② zip——for a, b in zip(list1, list2) 同时遍历多个序列(如「名字」和「分数」配对),以最短的那个为准(多余的丢弃),zip(*matrix) 还能「转置」(行列互换)。为什么不要 range(len(x))for i in range(len(seq)): x = seq[i] 既啰嗦又慢(每次索引访问),还容易越界/写错——enumerate 更 Pythonic、更快、更清晰。关键点:① 二者都返回「惰性迭代器」(不占内存、只能遍历一次);② zip 取最短、itertools.zip_longest 可补齐最长;③ dict(zip(keys, values)) 快速造字典。核心记忆:要下标用 enumerate(可 start=1)、要并行遍历多个序列用 zip(取最短、可转置),别用 range(len(x))

详细版

enumerate 和 zip 的用法

工具用法效果
enumerate(seq)for i, x in enumerate(seq)同时拿下标和值
enumerate(seq, 1)指定起始编号从 1 开始计数
zip(a, b)for x, y in zip(a, b)多序列并行遍历(取最短)
zip(*matrix)解包后配对矩阵转置(行列互换)
dict(zip(k, v))键值配对成字典快速造字典
names = ["Tom", "Amy", "Bob"]
scores = [85, 92, 78]

# enumerate:同时拿索引和元素
for i, name in enumerate(names):
    print(i, name)          # 0 Tom / 1 Amy / 2 Bob

# start 参数:从 1 开始编号
for rank, name in enumerate(names, start=1):
    print(f"第{rank}名: {name}")   # 第1名: Tom ...

# zip:并行遍历多个序列
for name, score in zip(names, scores):
    print(f"{name}: {score}")      # Tom: 85 ...

# zip 取最短
list(zip([1, 2, 3], ['a', 'b']))   # [(1,'a'), (2,'b')](3 被丢)

# zip 转置矩阵
matrix = [[1, 2, 3], [4, 5, 6]]
list(zip(*matrix))                 # [(1,4), (2,5), (3,6)](行列互换)

# dict(zip()) 造字典
dict(zip(names, scores))           # {'Tom':85, 'Amy':92, 'Bob':78}

# 对比:别这样写(range(len()) 反模式)
for i in range(len(names)):        # ✗ 啰嗦、慢、易错
    print(i, names[i])

⚠️ 一句话记住:需要「下标」用 enumerate,需要「同时遍历多个序列」用 zip,几乎永远不需要 range(len(x))——后者是从 C/Java 带来的思维惯性,在 Python 里既不地道也更慢range(len(seq)) 的问题:① 多打字、可读性差(要在循环体里 seq[i] 再取值);② 慢(每次 seq[i] 都是一次索引查找,而 enumerate/直接迭代是顺序取值);③ 容易出 bug(range(len(seq)-1)seq[i+1] 这类边界很容易错)。更重要的是理解它们的「惰性」本质:enumeratezip 返回的都是迭代器(不是列表),不会一次性构造所有配对、不占额外内存,适合大数据;但代价是只能遍历一次(遍历完就空了,要复用得先 list() 物化)。zip 还有个经典陷阱——取最短序列:如果两个列表长度不等,zip 会在最短的用完时停止、丢弃多余的,想「按最长补齐」得用 itertools.zip_longest(fillvalue=...)

完整版教学

一、enumerate:带索引遍历

先掌握 enumerate:

enumerate(iterable, start=0):
  遍历时同时产出 (索引, 元素)
  → 每次迭代返回一个 (i, value) 元组

基本用法:
  for i, x in enumerate(seq):
      # i 是索引(从 0 开始)、x 是元素
  → 元组自动解包成 i 和 x

start 参数:指定索引起始值
  enumerate(seq, start=1)    # 从 1 开始
  enumerate(seq, 100)        # 从 100 开始
  用途:排名、行号、人类友好的计数(从 1)

产出的是元组:
  list(enumerate(['a','b']))  → [(0,'a'), (1,'b')]
  不解包也行:for pair in enumerate(seq): pair[0], pair[1]

典型场景:
  ① 需要下标做定位/记录:
     for i, line in enumerate(lines, 1):
         if error: print(f"第{i}行出错")
  ② 边遍历边记位置:
     idx = next(i for i, x in enumerate(seq) if x == target)
  ③ 隔行处理:
     for i, x in enumerate(seq):
         if i % 2 == 0: ...

所以 enumerate(seq,start=0)遍历同时给(索引,元素),start 定起始编号(排名从 1)

enumerate(iterable, start=0) 遍历时同时产出 (索引, 元素)——每次迭代返回一个 (i, value) 元组。基本用法:for i, x in enumerate(seq)(元组自动解包成 i 和 x)。start 参数:指定索引起始值(enumerate(seq, start=1) 从 1 开始,用于排名、行号、人类友好计数)。产出的是元组:list(enumerate(['a','b']))[(0,'a'),(1,'b')]。典型场景:① 需要下标做定位/记录(enumerate(lines, 1) 报第几行出错)、② 边遍历边记位置、③ 隔行处理(if i % 2 == 0。理解「enumerate(seq,start=0)遍历同时给(索引,元素)、自动解包 i,x;start 定起始编号(排名/行号从 1);产出元组;场景定位/记位置/隔行」,就掌握了 enumerate。

二、zip:并行遍历多个序列

理解 zip 的用法:

zip(*iterables):把多个可迭代对象"按位置配对"
  zip(a, b) 每次产出 (a[i], b[i]) 元组
  → 多个序列"并行"遍历

基本用法:
  for x, y in zip(list1, list2):
      # x 来自 list1、y 来自 list2(同一位置)

多个序列(不止两个):
  for a, b, c in zip(l1, l2, l3):
      ...

产出元组:
  list(zip([1,2], ['a','b']))  → [(1,'a'), (2,'b')]

配合 enumerate(既要下标又要配对):
  for i, (name, score) in enumerate(zip(names, scores)):
      print(i, name, score)

典型场景:
  ① 配对处理两个相关列表:
     for name, age in zip(names, ages): ...
  ② 造字典:
     dict(zip(keys, values))
  ③ 逐列/逐行处理表格数据
  ④ 同时迭代多个数据源

对比 range(len):
  ✗ for i in range(len(names)):
        print(names[i], scores[i])
  ✓ for name, score in zip(names, scores):
        print(name, score)
  → zip 更清晰、无需索引

所以 zip(a,b,...)按位置配对多序列并行遍历,产出元组,dict(zip())造字典

zip(*iterables) 把多个可迭代对象「按位置配对」——zip(a, b) 每次产出 (a[i], b[i]) 元组、多个序列并行遍历。基本用法:for x, y in zip(list1, list2)(x 来自 list1、y 来自 list2 同一位置)。多个序列:for a, b, c in zip(l1, l2, l3)。产出元组:list(zip([1,2],['a','b']))[(1,'a'),(2,'b')]配合 enumerate(既要下标又要配对):for i, (name, score) in enumerate(zip(names, scores))。典型场景:① 配对处理两个相关列表、② 造字典 dict(zip(keys, values))、③ 逐列/逐行处理表格、④ 同时迭代多个数据源。对比 range(len):zip(names, scores)range(len(names))names[i], scores[i] 更清晰。理解「zip(a,b,…)按位置配对多序列并行遍历、产出元组;配合 enumerate 既要下标又要配对;场景配对处理/dict(zip())造字典/表格;比 range(len)清晰」,就掌握了 zip。

三、为什么不要 range(len(x))

理解 range(len(x)) 反模式的问题:

range(len(x)) 反模式(从 C/Java 带来的惯性):
  for i in range(len(seq)):
      x = seq[i]
      ...

三个问题:
  ① 啰嗦、可读性差:
     多一行 seq[i],循环体里到处是索引下标
     → enumerate 一步到位拿到 i 和 x

  ② 慢:
     seq[i] 每次都是一次"索引查找"(__getitem__)
     直接迭代/enumerate 是"顺序取下一个"(更快)
     大数据下差异明显

  ③ 易出 bug:
     range(len(seq)-1)、seq[i+1]、越界... 容易写错
     → 用 enumerate/zip 避免手动算索引

改写对照:
  只要值:       for x in seq         (根本不用索引)
  要索引和值:   for i, x in enumerate(seq)
  并行两个序列: for a, b in zip(la, lb)
  要索引配对:   for i,(a,b) in enumerate(zip(la,lb))

什么时候确实需要 range(len)(少数):
  ① 只要索引、不要值,且要复杂的索引运算
     (但通常也能用 enumerate 忽略值)
  ② 原地修改列表且逻辑依赖索引(seq[i] = f(seq[i]))
     → 也可 for i, x in enumerate(seq): seq[i] = f(x)
  → 绝大多数场景都有更 Pythonic 的替代

所以 range(len)啰嗦+慢(索引查找)+易错,用 enumerate/zip/直接迭代替代

range(len(x)) 反模式(从 C/Java 带来的惯性):for i in range(len(seq)): x = seq[i]三个问题① 啰嗦、可读性差(多一行 seq[i]、到处是索引下标,enumerate 一步拿到 i 和 x);② 慢seq[i] 每次是一次索引查找 __getitem__、直接迭代是顺序取下一个更快、大数据差异明显);③ 易出 bugrange(len(seq)-1)seq[i+1]、越界容易写错)。改写对照:只要值 for x in seq、要索引和值 for i, x in enumerate(seq)、并行两个序列 for a, b in zip(la, lb)、要索引配对 for i,(a,b) in enumerate(zip(la,lb))。什么时候确实需要 range(len)(少数):原地修改列表且依赖索引(也可用 enumerateseq[i] = f(x))。理解「range(len)三问题:啰嗦+慢(索引查找)+易错;改写只要值 for x in seq、要索引 enumerate、并行 zip;少数原地修改可用 enumerate 配 seq[i]=f(x)」,就理解了为什么不要 range(len)。

四、惰性迭代器——只能遍历一次

理解 enumerate/zip 的惰性本质:

enumerate 和 zip 返回"惰性迭代器"(不是列表):
  enumerate(seq) → <enumerate object>(迭代器)
  zip(a, b)      → <zip object>(迭代器)
  → 不一次性构造所有配对,边遍历边产出

好处:
  ① 省内存:不占额外空间(大数据/无限流友好)
  ② 惰性:只在遍历时才计算下一个

代价:只能遍历一次(遍历完就空了)
  z = zip(a, b)
  list(z)   → [(...), ...]  # 第一次:有内容
  list(z)   → []            # 第二次:空了!(迭代器耗尽)

  for x in z: ...   # 第一次遍历
  for x in z: ...   # 第二次:什么都不发生

要复用 → 先物化成列表:
  pairs = list(zip(a, b))   # 存成列表
  # 之后可多次遍历 pairs

对比 Python 2:
  Py2 的 zip 返回列表(可多次用,但占内存)
  Py3 改成迭代器(惰性、省内存,但只能遍历一次)
  → Py2 迁 Py3 时,靠 zip 返回列表的代码要加 list()

判断:
  只遍历一次 → 直接用(省内存)
  要多次用/要下标访问 → list(zip(...))

所以 enumerate/zip 是惰性迭代器(省内存但只遍历一次),复用要先 list()

enumerate 和 zip 返回「惰性迭代器」(不是列表)enumerate(seq) → enumerate object、zip(a,b) → zip object,不一次性构造所有配对、边遍历边产出。好处① 省内存(不占额外空间、大数据/无限流友好)、② 惰性(只在遍历时才计算下一个)代价:只能遍历一次(遍历完就空了)——z = zip(a,b); list(z) 第一次有内容、第二次空了(迭代器耗尽)。要复用先物化成列表pairs = list(zip(a, b)) 之后可多次遍历。对比 Python 2:Py2 的 zip 返回列表(可多次用但占内存)、Py3 改成迭代器(惰性省内存但只遍历一次),Py2 迁 Py3 时靠 zip 返回列表的代码要加 list()。理解「enumerate/zip 是惰性迭代器(不是列表)、省内存但只能遍历一次(耗尽就空);复用要先 list();Py2 返回列表、Py3 返回迭代器是迁移坑」,就掌握了惰性本质。

五、zip 的取最短陷阱与 zip_longest

理解 zip 长度不等时的行为:

zip 的"取最短"行为:
  多个序列长度不等时,zip 以"最短的"为准
  最短序列用完就停止,多余的元素被丢弃

  zip([1,2,3], ['a','b'])
  → [(1,'a'), (2,'b')]   # 3 被丢弃(第二个只有 2 个)

陷阱:
  以为 zip 会遍历完最长的 → 实际丢了数据(静默)
  data = zip(ids, names, ages)  # 若长度不一致,悄悄丢尾部
  → 长度不等且都想要 → 出 bug

要"按最长补齐" → itertools.zip_longest:
  from itertools import zip_longest
  list(zip_longest([1,2,3], ['a','b'], fillvalue='?'))
  → [(1,'a'), (2,'b'), (3,'?')]   # 短的用 fillvalue 补

检查长度一致(Python 3.10+):
  zip(a, b, strict=True)
  → 长度不等时抛 ValueError(而不是静默丢弃)
  → 想"必须等长"时用 strict=True 防 bug

zip(*iterable) 解包——转置:
  matrix = [[1,2,3],[4,5,6]]
  list(zip(*matrix))  → [(1,4),(2,5),(3,6)]
  → * 把每行拆成 zip 的参数,按列配对 = 转置
  (不等长时同样取最短,转置要求等长)

所以 zip 取最短(多余静默丢弃),补齐用 zip_longest,等长校验用 strict=True

zip 的「取最短」行为:多个序列长度不等时 zip 以「最短的」为准、最短用完就停止、多余的被丢弃(zip([1,2,3],['a','b'])[(1,'a'),(2,'b')],3 被丢)。陷阱:以为 zip 遍历完最长的、实际静默丢了数据(长度不等且都想要就出 bug)。要「按最长补齐」用 itertools.zip_longestzip_longest([1,2,3],['a','b'], fillvalue='?')[(1,'a'),(2,'b'),(3,'?')]检查长度一致(Python 3.10+)zip(a, b, strict=True) 长度不等时抛 ValueError(而不是静默丢弃、防 bug)。zip(*iterable) 解包——转置zip(*matrix) 把每行拆成参数、按列配对 = 转置。理解「zip 取最短(多余静默丢弃是陷阱);补齐用 zip_longest(fillvalue);等长校验用 strict=True(3.10+抛 ValueError);zip(*matrix)转置(按列配对)」,就掌握了 zip 的陷阱与相关工具。

六、总结与实践

总结 enumerate 和 zip:

enumerate 速查:
  for i, x in enumerate(seq)          # 索引+值
  for i, x in enumerate(seq, 1)       # 从 1 编号
  → 需要下标时用它,别用 range(len)

zip 速查:
  for a, b in zip(la, lb)             # 并行遍历
  dict(zip(keys, values))             # 造字典
  list(zip(*matrix))                  # 转置
  zip_longest(a, b, fillvalue=x)      # 补齐最长
  zip(a, b, strict=True)              # 校验等长(3.10+)

组合:
  for i, (a, b) in enumerate(zip(la, lb)):  # 下标+配对

反模式对照(改掉 range(len)):
  只要值        → for x in seq
  要索引        → for i, x in enumerate(seq)
  并行多序列    → for a, b in zip(la, lb)

关键特性:
  惰性迭代器(省内存、只遍历一次,复用先 list())
  zip 取最短(陷阱,补齐用 zip_longest、校验用 strict)

核心总结:
  要下标 → enumerate(可 start=1)
  并行遍历多序列 → zip(取最短、可转置、造字典)
  别用 range(len(x))(啰嗦、慢、易错)
  二者惰性、只遍历一次

所以 enumerate 拿下标、zip 并行遍历,别用 range(len),都是惰性只遍历一次

enumerate 速查for i, x in enumerate(seq)(索引+值)、enumerate(seq, 1)(从 1 编号)。zip 速查for a, b in zip(la, lb)(并行遍历)、dict(zip(keys, values))(造字典)、list(zip(*matrix))(转置)、zip_longest(a, b, fillvalue=x)(补齐)、zip(a, b, strict=True)(校验等长)。组合for i, (a, b) in enumerate(zip(la, lb))反模式对照:只要值 for x in seq、要索引 enumerate、并行 zip关键特性:惰性迭代器(省内存、只遍历一次、复用先 list)、zip 取最短(补齐 zip_longest、校验 strict)。理解「enumerate 拿下标(可 start=1)、zip 并行遍历(取最短/转置/造字典);别用 range(len);组合 enumerate(zip());惰性只遍历一次、zip 取最短要注意」,就掌握了总结与实践。

记忆钩子:「enumerate 和 zip 是 Python 遍历两大惯用工具:①enumerate(iterable,start=0)遍历时同时产出(索引,元素),for i,x in enumerate(seq)自动解包,enumerate(seq,start=1)从 1 编号(排名/行号);②zip(a,b,…)把多个序列『按位置配对』并行遍历,for a,b in zip(names,scores),dict(zip(keys,values))造字典,zip(*matrix)转置(按列配对);★别用 range(len(x))——那是 C/Java 惯性,啰嗦(循环体到处 seq[i])+慢(每次索引查找 getitem)+易错(越界/seq[i+1]),改写:只要值 for x in seq、要索引 enumerate、并行 zip;关键特性:enumerate/zip 都是『惰性迭代器』(不是列表)省内存但只能遍历一次(耗尽就空,复用要先 list(),Py3 改成迭代器是 Py2 迁移坑);★zip 陷阱:长度不等时『取最短』多余的静默丢弃,要按最长补齐用 itertools.zip_longest(fillvalue=…),要校验必须等长用 zip(a,b,strict=True)(3.10+不等长抛 ValueError)」

七、常见误区与追问

  • 误区:遍历时要用索引,就得用 range(len(seq))。 不需要——要「索引+值」用 enumerate(seq)for i, x in enumerate(seq) 直接拿到下标和值)、只要值用 for x in seq、并行遍历多个序列用 ziprange(len(seq)) 是从 C/Java 带来的思维惯性,在 Python 里更啰嗦(循环体里到处 seq[i])、更慢(每次索引查找)、更易出错(边界、越界),几乎总有更 Pythonic 的替代。
  • 误区:zip 会遍历完最长的序列。 恰恰相反——zip 以「最短的」序列为准,最短用完就停止、其余序列多余的元素被静默丢弃(zip([1,2,3],['a','b']) 只得两对、3 被丢);如果几个序列长度不等而你以为会全部遍历,就会悄悄丢数据;想「按最长补齐」用 itertools.zip_longest(fillvalue=...),想「强制等长否则报错」用 zip(a, b, strict=True)(Python 3.10+)。
  • 误区:enumerate(seq) 返回的可以反复遍历。 不行——enumerate 和 zip 返回的都是惰性迭代器(不是列表),只能遍历一次、遍历完就耗尽变空了;z = zip(a, b) 之后 list(z) 第一次有内容、第二次得到空列表;如果需要多次遍历或按索引访问,要先用 list() 物化成列表(pairs = list(zip(a, b)));这也是 Python 2 到 3 的一个迁移坑(Py2 的 zip 返回列表、Py3 返回迭代器)。
  • 误区:enumerate 的索引一定从 0 开始。 默认从 0,但可以用 start 参数改起始值——enumerate(seq, start=1) 从 1 开始编号(适合排名、行号等人类友好的计数)、enumerate(seq, 100) 从 100 开始;注意 start 只影响返回的「索引计数」、不影响从序列的哪个位置开始遍历(始终从头遍历整个序列)。
  • 追问:enumerate 和 zip 为什么比 range(len()) 更好? 三方面:① 可读性——for i, x in enumerate(seq) 一步拿到索引和值、for a, b in zip(la, lb) 直接并行取值,不用在循环体里反复写 seq[i]la[i];② 性能——直接迭代/enumerate 是「顺序取下一个元素」,而 seq[i] 每次是一次索引查找(__getitem__ 调用),大数据量下前者更快;③ 更安全——避免手动算索引带来的 range(len(seq)-1)seq[i+1] 越界这类边界 bug;此外 enumerate/zip 对任何可迭代对象(不只是支持索引的序列)都能用,而 range(len()) 只能用于有 len 和下标的序列。
  • *追问:zip(matrix) 为什么能转置矩阵? zip(*matrix) 里的 * 是解包操作符,把 matrix 的每一行(子列表)拆开作为 zip 的多个参数——比如 matrix = [[1,2,3],[4,5,6]]* 解包相当于 zip([1,2,3], [4,5,6]),zip 按位置配对就得到 [(1,4),(2,5),(3,6)],即原来的「第 j 列」变成了新的「第 j 个元组」,实现了行列互换(转置);注意各行长度不等时 zip 取最短、转置结果会丢数据,所以转置要求各行等长;结果是元组的列表,需要列表的列表可以 [list(row) for row in zip(*matrix)]
  • 追问:怎么用 zip 快速把两个列表合成字典?多次遍历 zip 结果要注意什么?dict(zip(keys, values)):zip 把 keys 和 values 按位置配成 (key, value) 元组序列,dict 直接把这些元组变成键值对,比如 dict(zip(['a','b'], [1,2])){'a':1, 'b':2}(若长度不等取最短、多余丢弃;若 keys 有重复,后面的覆盖前面的);要注意 zip 返回的是一次性迭代器,如果你先 z = zip(keys, values) 再既想 dict(z) 又想 list(z),第二次会得到空——迭代器已被第一次消费耗尽;需要复用同一个 zip 结果就先 pairs = list(zip(keys, values)) 物化,之后可多次使用。

八、加强记忆

enumeratezip 是 Python 遍历的两大惯用工具enumerate(iterable, start=0) 遍历时同时产出 (索引, 元素)——for i, x in enumerate(seq) 自动解包,enumerate(seq, start=1) 从 1 开始编号(排名/行号);zip(a, b, ...) 把多个序列「按位置配对」并行遍历——for a, b in zip(names, scores)dict(zip(keys, values)) 造字典、zip(*matrix) 转置(按列配对)。别用 range(len(x))——那是 C/Java 惯性:啰嗦(循环体到处 seq[i])+ 慢(每次索引查找)+ 易错(越界、seq[i+1]);改写:只要值 for x in seq、要索引 enumerate、并行 zip关键特性:enumerate/zip 都是惰性迭代器(不是列表)——省内存但只能遍历一次(耗尽就空,复用要先 list();Py3 改成迭代器是 Py2 迁移坑)。zip 陷阱:长度不等时**「取最短」、多余的静默丢弃**——按最长补齐用 itertools.zip_longest(fillvalue=...)、强制等长否则报错用 zip(a, b, strict=True)(3.10+)。一句话「要下标用 enumerate(可 start=1)、并行遍历多序列用 zip(取最短、可转置 zip(*m)、造字典 dict(zip()));别用 range(len)(啰嗦慢易错);二者是惰性迭代器只遍历一次(复用先 list());zip 取最短要注意(补齐 zip_longest、校验 strict=True)」。