字典自 Python3.7 起保序是语言规范,不是巧合
先排序再输出是旧认知
接手一段老代码,里面每次输出前都要对字典的键排一遍序,注释写着字典无序、必须排。在 Python3.7 之后这句话已经不成立,排序纯属多余。这不是我拍脑袋说的,把插入顺序和遍历顺序当面比一次就清楚。
插入顺序直接保住
d = {}
for k in ["z", "a", "m", "q", "c"]:
d[k] = 1
print(list(d.keys()))
print("python =", "3.13.14")
输出:
['z', 'a', 'm', 'q', 'c']
python = 3.13.14
打出来的顺序和插入时一字不差,没有按字母排,也没有被哈希打乱。在本机 Python3.13.14 上,字典遍历就是按插入顺序来的。
这是语言保证,不是巧合
Python3.6 在 CPython 实现里顺手让字典保序,当时还只是实现细节。到 3.7,这一点被正式写入语言规范(Python 语言参考明确记载),所有合规的实现都必须保序。所以它是承诺,不是某个版本碰巧做的优化,代码可以放心依赖。
那些先 sorted 再输出的注释,多半是 3.6 之前留下的,当年确实有必要,如今只会白白多花一次排序的时间,还容易让人误以为顺序不可靠。
删除和覆盖怎么影响顺序
覆盖一个已存在的键,位置不变。删掉中间的键,其余元素的相对先后也不变,删除只是把那个槽位标记为空,遍历时跳过。要想让某个键重新排到末尾,得先删再插入。
这些细节平时不太会注意,但一旦代码依赖了遍历顺序,就值得知道哪些操作会动位置、哪些不会。整体规律很简单:顺序由首次插入决定,之后的覆盖不影响它。
OrderedDict 现在还剩什么用
不是说 OrderedDict 彻底没用了。它仍提供普通字典没有的语义:move_to_end 可以把某个键挪到开头或末尾,popitem 能指定从哪头弹,这些在做 LRU 缓存、需要显式调整次序时很顺手。还有一个更细的差别:两个 OrderedDict 用等号比较时,会额外要求顺序也相同;普通字典则不比较顺序。只有当你正好需要顺序参与相等判断,或者要频繁挪动键的位置,才值得用它。单纯遍历保序,普通字典已经够。
字典相等与顺序无关
两个字典键和值都一样、插入顺序不同,用等号比较仍然相等。顺序只在遍历、转列表、序列化输出这些地方体现出来。所以为了判断相等而排序完全没必要,只有为了展示出稳定、可预期的顺序时才需要排。
这也提醒一件事:如果输出结果的顺序会影响下游,比如喂给一个按行解析的程序,那就别靠字典的默认顺序,显式按业务规则排一遍更稳妥。保序保证的是一致,不是符合某种业务排序。
实用做法
需要保序的集合,直接用普通 dict,别再套 OrderedDict,那是 3.7 之前的遗物。OrderedDict 现在只剩 move_to_end、需要额外顺序语义或者跟顺序相关的相等比较时才用得上。
想稳定复现遍历,字典本身就够;想按大小或字母序展示,才显式 sorted。把这两种需求分开,代码会清爽很多。
那段先排序再输出的注释,我最后删掉了,连带一次多余的排序。旧约束解除了,就该让它从代码里退场。
- 点赞
- 收藏
- 关注作者
评论(0)