Python中enumerate()函数带索引遍历的优雅写法
作者:星河耀银海
本文将带你全面掌握Python内置函数enumerate(),从基本语法到高级技巧,包括显示带编号列表、定位元素、与字典配合等场景,对比enumerate和range(len()),让你的代码更优雅高效
一、开篇:告别range(len()),拥抱enumerate()
每个Python程序员都经历过这个进化:
# 阶段1:C语言习惯
fruits = ["苹果", "香蕉", "橘子"]
i = 0
while i < len(fruits):
print(f"{i}: {fruits[i]}")
i += 1
# 阶段2:学会了range(len())
for i in range(len(fruits)):
print(f"{i}: {fruits[i]}")
# 阶段3:遇见enumerate()——Pythonic的觉醒
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
enumerate()是Python内置函数中最被低估的一个。它做的事情很简单——给可迭代对象的每个元素配上索引——但用好了,代码的优雅程度和可读性都会上一个台阶。
本文从基本用法到高级技巧,帮你成为enumerate()的使用高手。
二、enumerate()基本用法
2.1 语法和返回值
# enumerate(iterable, start=0)
# 返回一个enumerate对象,每次迭代产生 (index, value) 元组
fruits = ["苹果", "香蕉", "橘子", "葡萄"]
# 基本用法:默认从0开始
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
# 输出:
# 0: 苹果
# 1: 香蕉
# 2: 橘子
# 3: 葡萄
# 指定起始索引
for index, fruit in enumerate(fruits, start=1):
print(f"第{index}个: {fruit}")
# 输出:
# 第1个: 苹果
# 第2个: 香蕉
# 第3个: 橘子
# 第4个: 葡萄
# enumerate对象可以转为列表查看
print(list(enumerate(fruits)))
# [(0, '苹果'), (1, '香蕉'), (2, '橘子'), (3, '葡萄')]
print(list(enumerate(fruits, start=1)))
# [(1, '苹果'), (2, '香蕉'), (3, '橘子'), (4, '葡萄')]
2.2 enumerate()的本质
# enumerate() 是一个迭代器
# 它不会一次性创建所有(index, value)对
# 而是按需生成,内存效率高
# 手动实现一个简易的enumerate
def my_enumerate(iterable, start=0):
index = start
for item in iterable:
yield index, item
index += 1
# 测试
for i, char in my_enumerate("Python", start=1):
print(f"{i}: {char}")
# enumerate对象是惰性的
enum = enumerate(range(10000000)) # 不会创建1000万元组!
print(next(enum)) # (0, 0)
print(next(enum)) # (1, 1)
三、enumerate()的常见应用场景
3.1 场景一:显示带编号的列表
def display_menu(options):
"""显示带编号的菜单"""
print("请选择:")
for i, option in enumerate(options, start=1):
print(f" {i}. {option}")
options = ["新建文件", "打开文件", "保存文件", "另存为", "退出"]
display_menu(options)
# 输出:
# 请选择:
# 1. 新建文件
# 2. 打开文件
# 3. 保存文件
# 4. 另存为
# 5. 退出
3.2 场景二:找到匹配元素的位置
def find_all(items, predicate):
"""找到所有满足条件的元素的索引"""
return [i for i, item in enumerate(items) if predicate(item)]
scores = [85, 60, 92, 45, 78, 30, 95, 55]
# 找到所有不及格分数的位置
failing = find_all(scores, lambda x: x < 60)
print(f"不及格分数位置: {failing}")
print(f"不及格分数值: {[scores[i] for i in failing]}")
# 找到第一个满足条件的
def find_first(items, predicate):
for i, item in enumerate(items):
if predicate(item):
return i, item
return -1, None
idx, score = find_first(scores, lambda x: x >= 90)
print(f"第一个90分以上: 位置{idx}, 分数{score}")
3.3 场景三:同时遍历多个列表
names = ["张三", "李四", "王五", "赵六"]
ages = [25, 30, 28, 22]
cities = ["北京", "上海", "广州", "深圳"]
# enumerate + zip 同时获取索引和多个序列的值
for i, (name, age, city) in enumerate(zip(names, ages, cities), start=1):
print(f"{i}. {name}, {age}岁, 来自{city}")
# 或者构建结构化数据
users = [
{"id": i, "name": name, "age": age, "city": city}
for i, (name, age, city) in enumerate(zip(names, ages, cities), start=1001)
]
for user in users:
print(user)
3.4 场景四:与字典配合
# 从列表构建字典
fruits = ["苹果", "香蕉", "橘子", "葡萄"]
fruit_dict = {i: fruit for i, fruit in enumerate(fruits, start=1)}
print(fruit_dict) # {1: '苹果', 2: '香蕉', 3: '橘子', 4: '葡萄'}
# 反转:值→索引
fruit_to_index = {fruit: i for i, fruit in enumerate(fruits)}
print(fruit_to_index) # {'苹果': 0, '香蕉': 1, '橘子': 2, '葡萄': 3}
# 给字典的遍历加上序号
user = {"name": "张三", "age": 25, "city": "北京", "email": "zs@test.com"}
for i, (key, value) in enumerate(user.items(), start=1):
print(f"{i}. {key}: {value}")
3.5 场景五:文件行号
# 读取文件并标注行号
def read_file_with_line_numbers(filename):
"""读取文件,每行带行号"""
try:
with open(filename, 'r', encoding='utf-8') as f:
for line_no, line in enumerate(f, start=1):
yield line_no, line.rstrip('\n')
except FileNotFoundError:
print(f"文件不存在: {filename}")
# 使用
# for line_no, content in read_file_with_line_numbers("example.py"):
# print(f"{line_no:4d} | {content}")
# 查找特定行
def find_lines_containing(filename, keyword):
"""在文件中搜索包含关键词的行"""
results = []
for line_no, line in read_file_with_line_numbers(filename):
if keyword in line:
results.append((line_no, line))
return results
四、enumerate()的高级技巧
4.1 enumerate与列表推导式
# 创建带索引的数据结构
words = ["hello", "world", "python", "code"]
indexed_words = [(i, word.upper()) for i, word in enumerate(words)]
print(indexed_words)
# 条件筛选同时保留原索引
data = [3, 7, 0, 2, 8, 1, 4, 9, 5, 6]
# 找出所有值大于5的元素及其原位置
large_items = [(i, v) for i, v in enumerate(data) if v > 5]
print(large_items) # [(1, 7), (4, 8), (7, 9)]
# enumerate中嵌套解包
students = [("张三", 85), ("李四", 92), ("王五", 78)]
for rank, (name, score) in enumerate(students, start=1):
print(f"第{rank}名: {name} ({score}分)")
4.2 enumerate的对象可以多次迭代吗?
# enumerate是一个迭代器,只能遍历一次
enum = enumerate(["a", "b", "c"])
print("第一次遍历:")
for item in enum:
print(f" {item}")
print("第二次遍历:")
for item in enum:
print(f" {item}") # 什么都不输出!迭代器已耗尽
# ✅ 如果需要多次遍历,可以转为列表
enum_list = list(enumerate(["a", "b", "c"]))
# 现在可以多次遍历
for item in enum_list:
print(item)
4.3 enumerate与字典的妙用
# 从两个列表创建编号字典
names = ["张三", "李四", "王五"]
# 创建 {编号: 姓名} 的映射
id_to_name = dict(enumerate(names, start=1001))
print(id_to_name) # {1001: '张三', 1002: '李四', 1003: '王五'}
# 创建 {姓名: 编号} 的映射
name_to_id = {name: i for i, name in enumerate(names, start=1001)}
print(name_to_id) # {'张三': 1001, '李四': 1002, '王五': 1003}
4.4 在数据科学中的使用
# enumerate在数据预处理中很实用
# 例如:给数据集添加行号
raw_data = [
["张三", 25, "北京"],
["李四", 30, "上海"],
["王五", None, "广州"], # 缺失数据
["赵六", 22, "深圳"],
]
# 添加行号列,并标记异常行
cleaned = []
for idx, row in enumerate(raw_data, start=1):
name, age, city = row
status = "正常" if age is not None else "缺失年龄"
cleaned.append([idx, name, age, city, status])
for row in cleaned:
print(row)
# 找出所有问题行的序号
problem_rows = [i for i, (_, age, _) in enumerate(raw_data) if age is None]
print(f"问题行: {problem_rows}")
五、enumerate vs range(len()) 对比
5.1 为什么enumerate更好
items = ["a", "b", "c", "d", "e"]
# ❌ range(len()) —— 老式写法
for i in range(len(items)):
print(f"{i}: {items[i]}")
# ✅ enumerate() —— Pythonic写法
for i, item in enumerate(items):
print(f"{i}: {item}")
# enumerate 的优点:
# 1. 不需要用索引取元素(items[i]),更简洁
# 2. 避免索引越界错误
# 3. 对不支持索引的可迭代对象也能工作(如生成器、集合)
# 4. 代码意图更明确——"我要遍历并编号"
# enumerate 可以处理任何可迭代对象
def number_generator():
yield "x"
yield "y"
yield "z"
# range(len()) 不适用于生成器!
# for i in range(len(number_generator())): # TypeError!生成器没有len()
# enumerate() 完美适用
for i, val in enumerate(number_generator()):
print(f"{i}: {val}")
5.2 何时仍用range(len())
# 存几种场景range(len())更合适:
# 1. 修改原列表元素
numbers = [1, 2, 3, 4, 5]
for i in range(len(numbers)):
numbers[i] *= 10 # 直接修改原列表
# 如果用enumerate,修改的是迭代变量,不影响原列表
# 2. 访问相邻元素
data = [3, 1, 4, 1, 5, 9]
for i in range(len(data) - 1):
diff = data[i+1] - data[i]
print(f"data[{i}] → data[{i+1}]: {diff:+d}")
# 3. 跳跃遍历
for i in range(0, len(data), 2):
print(data[i])
# 但即便如此,有时enumerate配合条件也能做到
# 只是range(len())在这些场景下更直观
六、实战案例
6.1 排行榜生成器
class Leaderboard:
"""排行榜 —— enumerate()的自然使用场景"""
def __init__(self):
self.entries = []
def add_score(self, name, score):
self.entries.append({"name": name, "score": score})
# 保持降序排列
self.entries.sort(key=lambda x: x["score"], reverse=True)
def display(self, top_n=10):
"""显示排行榜"""
print("=" * 40)
print("🏆 排行榜 TOP", min(top_n, len(self.entries)))
print("=" * 40)
medals = {1: "🥇", 2: "🥈", 3: "🥉"}
for rank, entry in enumerate(self.entries[:top_n], start=1):
medal = medals.get(rank, f"{rank:2d}.")
name = entry["name"]
score = entry["score"]
bar = "█" * min(score // 10, 30)
print(f"{medal} {name:<10} {score:>5}分 {bar}")
def get_rank(self, name):
"""获取指定用户的排名"""
for rank, entry in enumerate(self.entries, start=1):
if entry["name"] == name:
return rank
return None
# 使用
lb = Leaderboard()
lb.add_score("张三", 950)
lb.add_score("李四", 820)
lb.add_score("王五", 780)
lb.add_score("赵六", 910)
lb.add_score("钱七", 880)
lb.add_score("孙八", 650)
lb.display(5)
print(f"\n李四的排名: 第{lb.get_rank('李四')}名")
6.2 差异对比工具
def compare_sequences(seq1, seq2):
"""对比两个序列,找出不同之处"""
differences = []
# 对比相同位置
min_len = min(len(seq1), len(seq2))
for i, (a, b) in enumerate(zip(seq1[:min_len], seq2[:min_len])):
if a != b:
differences.append({
"position": i,
"type": "mismatch",
"value1": a,
"value2": b,
})
# 处理长度差异
if len(seq1) > len(seq2):
for i, val in enumerate(seq1[min_len:], start=min_len):
differences.append({
"position": i,
"type": "extra_in_first",
"value1": val,
"value2": None,
})
elif len(seq2) > len(seq1):
for i, val in enumerate(seq2[min_len:], start=min_len):
differences.append({
"position": i,
"type": "extra_in_second",
"value1": None,
"value2": val,
})
return differences
# 测试
list1 = [1, 2, 3, 4, 5, 6, 7]
list2 = [1, 2, 0, 4, 0, 6]
diffs = compare_sequences(list1, list2)
for diff in diffs:
pos, typ = diff["position"], diff["type"]
if typ == "mismatch":
print(f"位置{pos}: {diff['value1']} ≠ {diff['value2']}")
elif typ == "extra_in_first":
print(f"位置{pos}: {diff['value1']} (仅在第一个序列中)")
elif typ == "extra_in_second":
print(f"位置{pos}: {diff['value2']} (仅在第二个序列中)")
七、本章小结
本文我们全面掌握了enumerate()函数:
- 基本用法:
enumerate(iterable, start=0)返回(index, value)元组的迭代器,默认从0开始,可通过start参数自定义起始值。 - 核心优势:惰性生成(不占内存)、适用于任何可迭代对象(不只是序列)、避免索引越界错误。
- 典型场景:编号列表、定位匹配元素、并行遍历(配合zip)、字典构建、文件行号标注。
- vs range(len()):对于"遍历+索引"的需求,enumerate()几乎总是比range(len())更好的选择。只有当需要修改原列表元素或访问相邻元素时,range(len())才更合适。
一个简单的原则:当你发现自己写了range(len(something))时,停下来想一想——是不是应该用enumerate()?99%的情况下答案是"是"。⌨️ 下一篇文章,我们将学习zip()函数——并行遍历多个序列的利器。
以上就是Python中enumerate()函数带索引遍历的优雅写法的详细内容,更多关于Python enumerate带索引遍历的资料请关注脚本之家其它相关文章!
