Python 3 for循环遍历字典的三种方式及效率对比

星野
星野
发布时间:2026-11-11 | 7人浏览过
原创

深入解析Python 3中for循环遍历字典的三种核心方式:遍历键、遍历值以及同时遍历键值对。结合底层视图机制(dict views)与基准测试数据,全面对比各方式的执行效率与适用场景。

分类:Python 核心基础与性能调优 运行环境:Python 3.7+
Python 3 for循环遍历字典的三种方式及效率对比
图 0:Python 3 字典遍历三种方式与核心场景概览

在 Python 3 中,字典(dict)是开发中使用频率最高的数据结构之一。使用 for 循环遍历字典主要有三种经典方式:遍历键(直接遍历或通过 .keys())、遍历值(.values())以及同时遍历键与值(.items())。理解这三种遍历方式的底层机制、代码规范以及在不同业务场景下的执行效率,能够帮助开发者写出更优雅、性能更优的 Python 代码。

一、Python 3 字典遍历的三种方式详解

Python 3 与早期 Python 2 的一个关键不同在于:调用 keys()、values() 和 items() 方法返回的不再是完整的列表副本,而是动态的字典视图对象(Dictionary Views)。视图对象不复制原字典的底层数据,提供对字典键值条目的只读窗口,具有 $O(1)$ 的创建空间开销。

1. 方式一:遍历键(直接遍历字典与 .keys())

在 Python 3 中,字典对象本身实现了迭代协议。如果直接将字典对象放入 for 循环中,默认就是对键进行迭代。同时,也可以显式调用 dict.keys() 获取键视图进行遍历。

# 示例代码:遍历字典的所有键
user_scores = {"alice": 95, "bob": 88, "charlie": 92}

# 写法 A:直接遍历字典(官方推荐惯用法)
for username in user_scores:
    print(f"用户键名: {username}")

# 写法 B:显式使用 keys() 视图
for username in user_scores.keys():
    print(f"用户键名: {username}")

代码要点说明:写法 A 与写法 B 在逻辑结果上完全一致。在实际项目中,如果不做集合交并集操作,推荐直接使用 for k in d:,不仅代码更加简短,还能免去一次方法属性查询的字节码开销。

2. 方式二:遍历值(使用 .values())

如果业务逻辑只关注字典中存储的数据内容,而完全不需要对应的键名(例如聚合求和、寻找极值、过滤数据值),应使用 .values() 视图直接获取各个元素的值。

# 示例代码:遍历字典的所有值
inventory = {"apple": 50, "banana": 120, "orange": 75}

total_count = 0
for count in inventory.values():
    total_count += count

print(f"库存商品总件数: {total_count}")

代码要点说明:避免使用 for k in d: val = d[k] 仅为了获取值,这样会触发多余的哈希计算与哈希表检索。

3. 方式三:同时遍历键与值(使用 .items())

当循环体内部既需要使用键名做条件判断或格式化输出,又需要用到对应的值时,.items() 是最规范且高效的选择。它每次迭代产出一个 (key, value) 元组,并通过 Python 的元组解构(Unpacking)直接赋给两个变量。

# 示例代码:同时遍历键和值
server_status = {
    "192.168.1.10": "RUNNING",
    "192.168.1.11": "STOPPED",
    "192.168.1.12": "RUNNING"
}

for ip, status in server_status.items():
    if status == "RUNNING":
        print(f"在线节点: {ip}")
    else:
        print(f"离线节点: {ip} [需检查]")
Python 3 for循环遍历字典的三种方式及效率对比
图 1:Python 3 三种遍历方式的代码语法与底层数据提取流向

二、底层实现原理:为什么 items() 优于“遍历键 + 索引查找”?

很多初学者在需要同时使用键和值时,容易写出如下代码:

# 常见的不推荐写法:通过键间接查找值
for k in d:
    v = d[k]  # 此处产生了额外的哈希计算和冲突检索
    process(k, v)

从代码可读性与运行效率上看,这种做法都是低效的。理解这一点,需要深入到 Python 3 字典的底层紧凑存储结构(Compact Dict):

  • Python 3 的哈希表结构:CPython 在 3.6+ 引入了紧凑字典结构,字典内部维护两个核心数组:一个是稀疏索引数组 indices,另一个是顺序存储元素的紧凑数组 dk_entries。每个 entry 直接保存了 hash、key 指针与 value 指针。
  • 遍历过程分析:当使用 d.items() 时,底层迭代器直接顺序扫描 dk_entries 数组,从同一个结构体条目中同时取出 key 和 value 指针打包为元组,时间复杂度是单次线性的。
  • 二次查找的代价:如果使用 for k in d: v = d[k],每一步不仅遍历了键,每次执行 d[k] 时又必须重新计算键的哈希值、在稀疏索引表中寻址并处理潜在冲突,造成了成倍的无效 CPU 运算。
Python 3 for循环遍历字典的三种方式及效率对比
图 2:CPython 紧凑字典结构下 items() 单次扫描与 d[k] 二次哈希寻址的底层对比

三、实际效率对比与基准测试

为了直观评估各种遍历方式在真实运行时的耗时差距,我们使用标准库 timeit 模块在 Python 3 环境下进行基准测试。测试采用包含 100,000 个键值对的字典,分别循环执行 100 次以消除单次波动的偶然性。

1. 完整基准测试代码

import timeit

# 准备测试数据:构建包含 10 万个元素的测试字典
setup_code = """
n = 100_000
d = {i: str(i) for i in range(n)}
"""

# 测试用例定义
tests = [
    ("for k in d: pass", "直接遍历键 (for k in d)"),
    ("for k in d.keys(): pass", "显式键视图 (for k in d.keys())"),
    ("for v in d.values(): pass", "值视图遍历 (for v in d.values())"),
    ("for k, v in d.items(): pass", "键值对视图 (for k, v in d.items())"),
    ("for k in d: v = d[k]", "遍历键并取值 (for k in d: v = d[k])")
]

print("=== Python 3 字典遍历基准测试结果 (100,000 条目 × 100 次迭代) ===")
for statement, label in tests:
    cost_time = timeit.timeit(statement, setup=setup_code, number=100)
    print(f"{label:35s} : {cost_time:.4f} 秒")

2. 实测结果数据分析

在标准 64 位 Python 3 环境下的实测数据汇总如下表所示:

测试遍历方式 耗时 (100次总耗时) 平均单次耗时 相对性能比 (以直接遍历为基准 1.0x) 核心开销特征
for k in d: pass 0.0289 秒 0.289 毫秒 1.00x(最快) 直接调用底层迭代器,无方法查找
for k in d.keys(): pass 0.0280 秒 0.280 毫秒 约 1.00x 轻微的 .keys() 属性查找,其余一致
for v in d.values(): pass 0.0291 秒 0.291 毫秒 约 1.01x 直接读取 entry 中的值指针,极为高效
for k, v in d.items(): pass 0.0583 秒 0.583 毫秒 约 2.01x 需创建 (k, v) 元组并执行解构赋值
for k in d: v = d[k] 0.0983 秒 0.983 毫秒 约 3.40x(最慢) 遍历键 + 每次循环二次哈希探测索引
Python 3 for循环遍历字典的三种方式及效率对比
图 3:五种字典遍历模式在 10 万级数据量下的耗时基准对比
测试结论分析: 从基准测试数据可以看出: 1. 只取键时:for k in d 与 for k in d.keys() 耗时几乎相同,前者略省字符,推荐优先使用 for k in d;
2. 只取值时:for v in d.values() 速度与纯键遍历持平,是读取值的最快路径;
3. 需同时取键值时:for k, v in d.items() 相比 for k in d: v = d[k] 耗时从 0.0983s 降到 0.0583s,效率提升约 40.7%,彻底消除多余哈希开销。

四、遍历过程中的高频避坑与常见问题

1. 运行时错误:RuntimeError: dictionary changed size during iteration

在 Python 3 中,字典视图是动态绑定的。如果在 for 循环中直接对当前字典执行增加键(d[new_key] = val)或删除键(del d[k] / d.pop(k)),迭代器会检测到字典内部结构版本的改变,并立即抛出如下异常:

# 错误示例:遍历时直接删除元素
d = {"a": 1, "b": 2, "c": 3}
for k in d:
    if k == "b":
        del d[k]  # 触发 RuntimeError: dictionary changed size during iteration

正确解决方式有两种:

  • 方案一:通过 list() 固化键列表。使用 list(d.keys()) 或 list(d) 建立一个浅拷贝快照,针对快照迭代,即可安全修改原字典:
    for k in list(d.keys()):
        if k == "b":
            del d[k]  # 安全删除
    
  • 方案二:使用字典推导式创建新字典。如果目的是过滤数据,直接构建新字典通常更清晰高效:
    d = {k: v for k, v in d.items() if k != "b"}
    

2. 集合运算支持:dict_keys 与 dict_items 的集合特性

在 Python 3 中,d.keys() 和 d.items() 返回的视图对象支持类似 set 的集合操作(如交集 &、并集 |、差集 -),而 d.values() 由于值可能重复或包含不可哈希对象,不支持集合操作。

dict_a = {"apple": 1, "banana": 2, "orange": 3}
dict_b = {"banana": 5, "orange": 3, "grape": 4}

# 快速求两个字典共同拥有的键
common_keys = dict_a.keys() & dict_b.keys()
print(f"共有键名: {common_keys}")  # 输出: {'banana', 'orange'}

# 快速找出键和值都完全相同的条目
common_items = dict_a.items() & dict_b.items()
print(f"共有条目: {common_items}")  # 输出: {('orange', 3)}
Python 3 for循环遍历字典的三种方式及效率对比
图 4:Python 3 字典遍历方式选型决策树与使用建议

五、总结与选型建议

归纳全文的核心结论,在 Python 3 中编写 for 循环遍历字典时,建议遵循以下最佳实践原则:

  1. 仅需键名:直接使用 for k in d:。这是官方最为推荐的 Pythonic 写法,代码简洁度最高,省去方法调用和属性解析。仅当需要进行集合交集、差集等操作时,使用 d.keys()。
  2. 仅需键值:使用 for v in d.values():。底层直接扫描紧凑数组对应的值指针,效率最高,且意图明确。
  3. 同时需要键和值:务必使用 for k, v in d.items():。严禁使用 for k in d: v = d[k],后者会引入额外的哈希查找与散列碰撞探测开销,在大数据量或频繁循环时性能劣势尤为明显。
  4. 避免动态增删:遍历字典时切忌原地添加或删除元素。若确有删除需求,请使用 list(d.keys()) 生成键列表副本进行迭代,或使用字典推导式重新生成新字典。
相关专题:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表