python 3.6起dict“有序”是因cpython改用紧凑哈希表实现,仅保证插入顺序;3.7+才正式成为语言规范,但不支持按键排序或索引访问,仍非序列类型。

dict 在 Python 3.6 之前无序,是因为它用的是传统开放寻址哈希表;3.6 起“变有序”,本质是 CPython 换了底层存储结构,不是加了排序逻辑。
Python 3.5 及更早:哈希值决定遍历顺序,天然不可预测
早期 dict 的键值对直接存进一个散列表(entries 数组),索引由 hash(key) % table_size 算出。由于 Python 启动时会随机化哈希种子(防 DOS 攻击),同一段代码在不同运行中,hash("a") 可能不同 → 插入位置不同 → 遍历顺序每次都不一样。
常见错误现象:
-
for k in d:输出顺序不一致,导致测试偶然失败 - JSON 序列化后字段乱序,前端依赖字段顺序时出错
- 误以为
dict是“按字母排序”,实际既不按插入也不按键排序
Python 3.6:紧凑哈希表让插入顺序“顺带保留”
CPython 3.6 把 dict 拆成两部分:indices(小数组,只存索引) + entries(大数组,按插入顺序存键值对)。查找时仍靠哈希算索引,但遍历时只需顺序读 entries —— 插入顺序自然就保住了。
关键点:
- 这是 CPython 实现优化,不是语言规范要求,PyPy、Jython 当时没同步
-
dict相等性比较仍忽略顺序:{'a':1,'b':2} == {'b':2,'a':1}返回True - 不能靠
list(d.keys())[0]做业务逻辑,3.6 中虽大概率稳定,但官方文档仍写“不保证”
Python 3.7+:插入顺序正式成为语言特性
PEP 520 和 PEP 468 把“保持插入顺序”写进语言规范,所有合规解释器都必须实现。这意味着:
-
json.loads()返回的dict会保持原始 JSON 字段顺序 -
**kwargs传参顺序在函数内可被可靠访问(影响装饰器、参数校验等场景) -
collections.OrderedDict不再为“顺序”而存在,仅用于move_to_end()或popitem(last=False)这类特殊操作
但要注意:dict 依然没有 .sort() 方法,也没有 key 参数;所谓“有序”,仅指插入顺序,不是按键排序。想按键排,还得写 dict(sorted(d.items()))。
容易被忽略的细节:有序 ≠ 可靠索引访问
很多人看到“有序”就下意识当 list 用,比如写 d.keys()[0] 或 next(iter(d)) 来取第一个键 —— 这在 3.7+ 是安全的,但前提是:你确定这个 dict 是新构造的、没被反复赋值覆盖过键。
真正危险的是这种模式:
d = {}
d['x'] = 1
d['y'] = 2
d['x'] = 99 # 修改已有键,不改变顺序
# 此时 d.keys() 仍是 ['x', 'y'],没问题
但如果混用 update() 或字面量解包:
d.update({'z': 3, 'a': 4}) # 新键追加到末尾,顺序可控
d = {**d, 'b': 5} # Python 3.9+ 才保证顺序继承,旧版可能打乱
所以别把“有序”当成“可预测索引容器”——它只是让遍历行为变得稳定,不是让你把它当有序序列类型使。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











