Effective Python 条款 7:告别range下标,善用enumerate与zip写出清爽代码
- Bilibili 同步视频
- 别再用 range + 下标遍历序列
- enumerate:优雅获取循环序号的神器
-
- 简单小案例:统计二进制位
- 写在后面:即将登场的 zip 并行遍历
-
- ✨最佳实践速记
写 Python 最怕什么?满屏的下标、生硬的range(len(list))循环,代码绕来绕去,读起来费脑子,改起来容易踩坑。很多新手刚上手时,习惯用其他语言的思维写循环,写出一堆不够 Pythonic 的代码。今天我们就聊聊迭代场景下两个宝藏内置函数:enumerate 和 zip,帮你把循环写得简洁又优雅✨。
Bilibili 同步视频
Effective Python 条款 7:告别range下标,善用enumerate与zip写出清爽代码
别再用 range + 下标遍历序列
很多刚从 C/C++ 转 Python 的开发者,遍历列表需要获取元素位置时,第一反应会写出下面这种代码:
flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry']
# 不推荐的写法
for i in range(len(flavor_list)):
flavor = flavor_list[i]
print(f"{i + 1}: {flavor}")
这段代码逻辑没问题,可以正常输出口味和对应的排名。但它有几个很明显的短板:
先要调用len()获取列表长度,再用range生成下标数字,步骤繁琐;
循环拿到下标之后,还要再通过下标去取列表元素,多了一层索引访问;
代码阅读成本高,一眼看过去分不清i到底代表什么,后续维护很容易因为下标偏移出现 bug。
💡Python 的哲学:直接迭代容器本身,而不是迭代下标数字。 如果我们只需要元素,完全不需要下标,直接遍历列表就足够了:
flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry']
for flavor in flavor_list:
print(f"{flavor} is delicious")
这种写法干净利落,不需要维护索引变量,可读性直接拉满。但现实开发里,我们经常既要拿到元素,又要拿到它的序号,这时候该怎么办?
enumerate:优雅获取循环序号的神器
Python 内置enumerate就是专门解决这个痛点的工具。 enumerate会接收一个迭代器,把它包装成一个惰性生成器。每次迭代的时候,会同时返回序号 + 当前元素这一组数据,配合 Python 的解包语法,一行 for 循环就能搞定两件事。
我们可以手动调用next(),直观感受它的内部输出:
flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry']
it = enumerate(flavor_list)
print(next(it))
print(next(it))
输出:
(0, 'vanilla')
(1, 'chocolate')
可以看到每一轮返回的是元组,第一个值是索引,第二个是列表元素。利用解包,直接写到 for 循环变量中:
flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry']
for i, flavor in enumerate(flavor_list):
print(f"{i + 1}: {flavor}")
默认序号是从 0 开始,所以我们需要手动i+1做排名。
但是!enumerate还藏了一个超好用的第二参数,可以直接指定起始编号,不用循环内部做数字偏移:
flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry']
# 第二个参数设置起始序号,直接从1开始计数
for i, flavor in enumerate(flavor_list, 1):
print(f"{i}: {flavor}")
输出结果:
1: vanilla
2: chocolate
3: pecan
4: strawberry
✅enumerate核心要点总结:
它封装迭代器,惰性产出 (序号,元素),不会一次性把全部数据加载内存;
优先替代range(len(xxx))这种下标遍历写法;
第二个入参可以自定义起始序号,默认值为 0;
配合解包语法,代码清爽,阅读者一眼看懂变量含义。
📌小补充:对比性能,enumerate和range+下标总体性能差距很小,它的核心收益不是跑的更快,而是降低代码出错概率,提升可读性。写业务代码,可读性往往比几纳秒的性能更重要。
简单小案例:统计二进制位
再看一个小例子,用 range 遍历整数序列是合适的,因为我们本身就是要操作数字本身:
from random import randint
random_bits = 0
for i in range(32):
if randint(0, 1):
random_bits |= 1 << i
print(bin(random_bits))
划重点:range适合迭代数字本身;而遍历容器、列表、字符串这类序列,需要序号优先选enumerate,不要强行用 range 做下标。
写在后面:即将登场的 zip 并行遍历
实际开发中,除了单列表带序号遍历,我们还经常遇到需要同时遍历两个甚至多个列表的场景,比如源列表生成派生列表,一一对应处理两组数据。 这时候就轮到zip出场,它可以把多个迭代器打包在一起同步迭代,避免手动维护下标去对齐两组数据,同样可以规避大量下标相关的 bug。
✨最佳实践速记
| 只取元素,不需要序号 | for item in list | for i in range(len(list)) |
| 需要元素 + 序号 | for i,item in enumerate(list, start=1) | for i in range(len(list)): item = list[i] |
| 需要单纯生成数字序列 | range(n) | 不要强行构造列表来生成数字 |
Pythonic 的代码,本质就是少写手动下标、多用语言内置工具和解包,让代码表达业务逻辑,而不是表达循环索引的细节。把繁琐的索引管理交给语言内置函数,我们专注业务逻辑本身。
小结
迭代序列优先直接遍历对象,不要无脑用 range 生成下标访问;
需要序号时,优先使用enumerate,支持自定义起始序号;
range适合的场景是迭代数字,而不是用来拿容器下标;
善用解包语法,让循环变量语义清晰,降低后续维护成本。

后续我们继续拆解zip的用法,聊聊并行遍历多个迭代器的坑与技巧。



