为什么需要 enumerate() 和 zip()?
在学习 for 循环之后,我们已经可以很方便地遍历列表中的元素。
例如:
languages = ["Spanish", "English", "Russian", "Chinese"]
for language in languages:
print(language)
输出:
Spanish
English
Russian
Chinese
这种写法可以直接获取列表中的元素。
但是,在实际编程时,我们经常会遇到另外两种情况:
- 遍历元素的同时,还需要知道当前元素的索引。
- 需要同时遍历两个或多个可迭代对象。
Python 分别提供了两个非常方便的函数:
| 函数 | 作用 |
|---|---|
enumerate() |
遍历时同时获取索引和值 |
zip() |
按位置组合多个可迭代对象 |
下面分别来看它们是如何工作的。
enumerate():遍历时获取索引和值
enumerate() 是 Python 的内置函数。
它的主要作用是:
在遍历一个可迭代对象时,同时提供每个元素的索引和值。
基本语法:
enumerate(iterable, start=0)
参数说明:
| 参数 | 说明 |
|---|---|
iterable |
要遍历的可迭代对象,例如列表、元组、字符串等 |
start |
索引的起始值,默认为 0 |
不使用 enumerate() 获取索引
假设现在有一个语言列表:
languages = ["Spanish", "English", "Russian", "Chinese"]
如果我们希望同时打印每种语言和它的索引,可以手动创建一个 index 变量:
languages = ["Spanish", "English", "Russian", "Chinese"]
index = 0
for language in languages:
print(f"Index {index} and language {language}")
index += 1
输出:
Index 0 and language Spanish
Index 1 and language English
Index 2 and language Russian
Index 3 and language Chinese
这种写法当然没有问题,但需要自己完成两件事情:
- 创建
index变量。 - 每次循环结束后手动执行
index += 1。
如果只是为了获得列表索引,这种写法就显得比较繁琐。
使用 enumerate() 获取索引
使用 enumerate() 后,可以直接获得索引和值:
languages = ["Spanish", "English", "Russian", "Chinese"]
for index, language in enumerate(languages):
print(f"Index {index} and language {language}")
输出:
Index 0 and language Spanish
Index 1 and language English
Index 2 and language Russian
Index 3 and language Chinese
可以看到,我们不再需要手动创建和更新 index。
enumerate() 会自动帮我们记录当前元素的索引。
enumerate() 是怎么工作的?
来看下面这段代码:
languages = ["Spanish", "English", "Russian", "Chinese"]
result = enumerate(languages)
print(list(result))
输出:
[(0, "Spanish"), (1, "English"), (2, "Russian"), (3, "Chinese")]
可以把结果理解成一组“索引 + 元素”的组合:
| 组合 | 含义 |
|---|---|
(0, "Spanish") |
索引 0 对应 "Spanish" |
(1, "English") |
索引 1 对应 "English" |
(2, "Russian") |
索引 2 对应 "Russian" |
(3, "Chinese") |
索引 3 对应 "Chinese" |
这里需要注意:
enumerate()返回的是一个enumerate对象,而不是列表。
我们使用:
list(enumerate(languages))
只是为了把它转换成列表,从而方便查看里面的数据。
为什么 for 中可以写 index, language?
可能有初学者会疑惑:
for index, language in enumerate(languages):
为什么一次循环可以得到两个变量?
原因是 enumerate() 产生的每一项实际上都是一个包含两个元素的元组。
例如第一次循环得到:
(0, "Spanish")
然后 for 会自动进行元组解包(Tuple Unpacking):
(0, "Spanish")
↓
解包
↓
index = 0
language = "Spanish"
第二次循环:
(1, "English")
↓
解包
↓
index = 1
language = "English"
所以:
for index, language in enumerate(languages):
可以理解为:
每次从
enumerate()中取出一个(索引, 元素)元组,然后分别赋值给index和language。
enumerate(start=...):修改索引起点
默认情况下,enumerate() 从 0 开始计数。
例如:
languages = ["Spanish", "English", "Russian", "Chinese"]
for index, language in enumerate(languages):
print(index, language)
输出:
0 Spanish
1 English
2 Russian
3 Chinese
如果希望从 1 开始编号,可以设置 start 参数:
languages = ["Spanish", "English", "Russian", "Chinese"]
for index, language in enumerate(languages, start=1):
print(index, language)
输出:
1 Spanish
2 English
3 Russian
4 Chinese
也可以写成:
enumerate(languages, 1)
这里的 1 就是 start 参数。
start 参数是如何工作的?
例如:
letters = ["A", "B", "C"]
print(list(enumerate(letters, start=5)))
输出:
[(5, "A"), (6, "B"), (7, "C")]
可以看到:
| 索引 | 元素 |
|---|---|
5 |
"A" |
6 |
"B" |
7 |
"C" |
start 只决定第一个索引是多少,之后的索引仍然会依次递增。
enumerate() 不只可以遍历列表
只要对象是可迭代对象(Iterable),通常都可以使用 enumerate()。
例如,可以遍历字符串:
for index, char in enumerate("code"):
print(index, char)
输出:
0 c
1 o
2 d
3 e
这里:
index表示字符的位置。char表示当前字符。
也可以遍历元组:
numbers = (10, 20, 30)
for index, number in enumerate(numbers):
print(index, number)
输出:
0 10
1 20
2 30
因此,enumerate() 并不是只能用于列表。
zip():同时遍历多个对象
前面介绍的 enumerate() 主要解决的是:
遍历一个对象时,如何同时获得索引和值?
那么,如果我们需要同时遍历多个可迭代对象呢?
这时候可以使用 zip()。
zip() 是 Python 的内置函数,它可以:
按照位置,将多个可迭代对象中的元素组合起来。
基本语法:
zip(iterable1, iterable2, ...)
例如:
developers = ["Naomi", "Dario", "Jessica", "Tom"]
ids = [1, 2, 3, 4]
print(list(zip(developers, ids)))
输出:
[("Naomi", 1), ("Dario", 2), ("Jessica", 3), ("Tom", 4)]
zip() 是怎么工作的?
假设有两个列表:
developers
索引: 0 1 2 3
↓ ↓ ↓ ↓
Naomi Dario Jessica Tom
ids
索引: 0 1 2 3
↓ ↓ ↓ ↓
1 2 3 4
zip() 会按照相同的索引位置进行配对:
位置 0
↓
("Naomi", 1)
位置 1
↓
("Dario", 2)
位置 2
↓
("Jessica", 3)
位置 3
↓
("Tom", 4)
最终得到:
[
("Naomi", 1),
("Dario", 2),
("Jessica", 3),
("Tom", 4)
]
所以,zip() 可以简单理解成:
把多个对象中相同位置的元素“拉”到一起。
这也是 zip(拉链)这个名字非常形象的地方。
使用 zip() 配合 for 循环
zip() 最常见的使用方式,就是直接配合 for 循环:
developers = ["Naomi", "Dario", "Jessica", "Tom"]
ids = [1, 2, 3, 4]
for name, dev_id in zip(developers, ids):
print(f"Name: {name}")
print(f"ID: {dev_id}")
输出:
Name: Naomi
ID: 1
Name: Dario
ID: 2
Name: Jessica
ID: 3
Name: Tom
ID: 4
这里同样发生了元组解包。
第一次循环时,zip() 产生:
("Naomi", 1)
然后自动解包为:
name = "Naomi"
dev_id = 1
第二次循环:
("Dario", 2)
自动变成:
name = "Dario"
dev_id = 2
所以:
for name, dev_id in zip(developers, ids):
可以理解为:
每次从
zip()中取出一个(姓名, ID)元组,然后分别赋值给name和dev_id。
zip() 返回的是什么?
和 enumerate() 一样,zip() 默认也不会直接返回列表。
例如:
developers = ["Naomi", "Dario"]
ids = [1, 2]
pairs = zip(developers, ids)
print(type(pairs))
输出类似:
<class 'zip'>
说明 pairs 是一个 zip 对象。
如果想查看其中的数据,可以转换成列表:
print(list(zip(developers, ids)))
输出:
[("Naomi", 1), ("Dario", 2)]
这里的 list() 主要是为了方便查看结果。
在实际循环中,通常不需要先转换成列表:
for name, dev_id in zip(developers, ids):
print(name, dev_id)
zip() 可以组合多个对象
zip() 并不限制只能组合两个对象。
例如,可以同时组合姓名、年龄和职业:
names = ["Alice", "Bob", "Charlie"]
ages = [20, 22, 25]
jobs = ["Python Developer", "Web Developer", "Data Analyst"]
for name, age, job in zip(names, ages, jobs):
print(f"{name} is {age} years old and works as a {job}.")
输出:
Alice is 20 years old and works as a Python Developer.
Bob is 22 years old and works as a Web Developer.
Charlie is 25 years old and works as a Data Analyst.
实际上,zip() 会产生:
("Alice", 20, "Python Developer")
("Bob", 22, "Web Developer")
("Charlie", 25, "Data Analyst")
然后 for 循环再把每个元组解包成三个变量。
zip() 遇到长度不同的对象怎么办?
这是使用 zip() 时非常重要的一点。
例如:
names = ["Alice", "Bob", "Charlie"]
ages = [20, 22]
print(list(zip(names, ages)))
输出:
[("Alice", 20), ("Bob", 22)]
为什么 "Charlie" 没有出现在结果中?
因为两个列表的长度不同:
names:3 个元素
ages: 2 个元素
zip() 默认会在最短的可迭代对象耗尽时停止。
可以理解为:
Alice ↔ 20 ✅
Bob ↔ 22 ✅
Charlie ↔ 没有数据 ❌
因此,"Charlie" 会被忽略。
这意味着:
使用
zip()时,如果多个数据源长度不同,默认只会处理能够一一对应的部分。
enumerate() 和 zip() 可以一起使用
有时候,我们既需要:
- 当前数据的索引;
- 又需要同时遍历多个对象。
这时可以把 enumerate() 和 zip() 组合起来。
例如:
names = ["Alice", "Bob", "Charlie"]
ages = [20, 22, 25]
for index, (name, age) in enumerate(zip(names, ages), start=1):
print(f"{index}. {name} - {age} years old")
输出:
1. Alice - 20 years old
2. Bob - 22 years old
3. Charlie - 25 years old
这个写法可以分成两步理解。
第一步,zip() 负责把姓名和年龄配对:
("Alice", 20)
("Bob", 22)
("Charlie", 25)
第二步,enumerate() 再为这些数据添加索引:
1 → ("Alice", 20)
2 → ("Bob", 22)
3 → ("Charlie", 25)
最后通过元组解包:
for index, (name, age) in enumerate(zip(names, ages), start=1):
得到三个变量:
index
name
age
因此,这种组合方式可以理解成:
zip()负责“配对数据”,enumerate()负责“添加索引”。
enumerate() 和 zip() 的区别
虽然这两个函数都经常与 for 循环一起使用,但解决的问题并不相同。
| 函数 | 主要作用 | 结果特点 |
|---|---|---|
enumerate() |
获取索引和值 | (索引, 元素) |
zip() |
同时遍历多个对象 | (元素1, 元素2, ...) |
例如:
languages = ["Python", "Java", "Rust"]
for index, language in enumerate(languages):
print(index, language)
这里关注的是:
索引 + 元素
而:
languages = ["Python", "Java", "Rust"]
versions = [3, 21, 1]
for language, version in zip(languages, versions):
print(language, version)
这里关注的是:
元素 + 元素
可以用一句话区分:
enumerate()解决“我想知道它排第几个”的问题。
zip()解决“我想同时处理几组对应数据”的问题。
enumerate() 和 range() 有什么区别?
初学者还容易把 enumerate() 和 range() 混淆。
假设要遍历一个列表,并同时获取索引和值。
可以使用 range():
languages = ["Python", "Java", "Rust"]
for index in range(len(languages)):
print(index, languages[index])
也可以使用 enumerate():
languages = ["Python", "Java", "Rust"]
for index, language in enumerate(languages):
print(index, language)
两种方式都能实现相同的效果,但 enumerate() 更直接。
对比:
range(len(...)) |
enumerate() |
|---|---|
| 先生成索引 | 直接获得索引和值 |
| 需要通过索引访问元素 | 直接获得元素 |
| 代码相对繁琐 | 代码更加简洁 |
| 容易出现索引相关问题 | 更符合 Python 的常见写法 |
因此,一般来说:
如果只是为了在遍历时同时获取索引和值,优先使用
enumerate()。
enumerate() 和 zip() 的常见使用场景
可以简单记住下面这张表:
| 需求 | 推荐使用 |
|---|---|
| 只遍历一个对象 | for |
| 遍历时需要索引和值 | enumerate() |
| 同时遍历两个对象 | zip() |
| 同时遍历多个对象 | zip() |
| 同时遍历多个对象,并且需要索引 | enumerate(zip(...)) |
例如:
遍历列表,并显示编号
↓
enumerate()
遍历姓名和成绩
↓
zip()
遍历姓名、成绩,并显示编号
↓
enumerate(zip(...))
常见错误
❌ 为了获取索引而手动维护变量
这种写法虽然可以使用:
index = 0
for language in languages:
print(index, language)
index += 1
但如果只是为了获取索引,更推荐:
for index, language in enumerate(languages):
print(index, language)
这样不需要自己管理 index。
❌ 误以为 enumerate() 返回列表
例如:
result = enumerate(["A", "B", "C"])
print(type(result))
得到的是:
<class 'enumerate'>
如果只是想查看里面的数据,可以:
print(list(enumerate(["A", "B", "C"])))
输出:
[(0, "A"), (1, "B"), (2, "C")]
所以要区分:
enumerate(...)
↓
enumerate 对象
list(enumerate(...))
↓
列表
❌ 误以为 zip() 会自动保留较长对象的数据
例如:
names = ["Alice", "Bob", "Charlie"]
ages = [20, 22]
for name, age in zip(names, ages):
print(name, age)
输出:
Alice 20
Bob 22
Charlie 不会被处理,因为 ages 已经没有第三个元素。
因此使用 zip() 时,要注意多个数据源之间的长度是否符合预期。
enumerate() 与 zip() 知识速查表
| 写法 | 作用 | 返回对象 |
|---|---|---|
enumerate(iterable) |
获取索引和值 | enumerate 对象 |
enumerate(iterable, start) |
指定索引起点 | enumerate 对象 |
zip(a, b) |
按位置组合两个对象 | zip 对象 |
zip(a, b, c) |
按位置组合多个对象 | zip 对象 |
list(enumerate(...)) |
查看枚举结果 | 列表 |
list(zip(...)) |
查看组合结果 | 列表 |
学习小结
enumerate()
enumerate() 适合在遍历时同时获取索引和值:
languages = ["Python", "Java", "Rust"]
for index, language in enumerate(languages):
print(index, language)
需要记住:
- 默认从
0开始计数。 - 可以通过
start修改起始值。 - 每次产生的数据可以理解为
(索引, 元素)。 - 返回的是
enumerate对象,而不是列表。 - 经常与
for循环搭配使用。
zip()
zip() 适合同时遍历多个可迭代对象:
names = ["Alice", "Bob"]
ages = [20, 22]
for name, age in zip(names, ages):
print(name, age)
需要记住:
- 按位置将多个对象中的元素组合起来。
- 每次产生的数据可以理解为一个元组。
- 可以同时组合两个或多个对象。
- 返回的是
zip对象,而不是列表。 - 默认在最短的可迭代对象耗尽时停止。
总结
enumerate()用于在遍历可迭代对象时,同时获得索引和值。enumerate()默认从0开始计数,可以通过start修改起始值。enumerate()返回enumerate对象,每一项可以理解为(索引, 元素)。zip()用于将多个可迭代对象按照位置进行组合。zip()返回zip对象,每一项可以理解为由多个对象对应元素组成的元组。zip()可以同时组合两个或多个可迭代对象。- 当对象长度不一致时,
zip()默认以最短对象为准停止。 enumerate()和zip()都非常适合与for循环配合使用。- 两者还可以组合起来,实现“索引 + 多组对应数据”的遍历。
一句话记忆:
enumerate()= 索引 + 元素;zip()= 多个对象按位置配对。