有序字典是Python3.7及其之后版本的内置类型。相比于普通字典,它可以保持插入顺序,并且支持一些新的方法。除了这些显而易见的特点,有序字典还有两个小“惊喜”:

1. 最后一个键值对可被序列化
在Python3.7之前,我们可以使用pickle库来将有序字典序列化。但是pickle库有一个限制:它不能很好地处理最后一个键值对。换句话说,如果你将序列化的有序字典加载回来,最后一个键值对可能不会被还原。这是因为pickle库在序列化字典时使用一个标记来指示字典结束,而最后一个键值对并没有被标记。
幸运的是,Python3.7引入了一种新的序列化协议:“pickle protocol 4”。这个协议修复了pickle库的这个限制,使得有序字典的最后一个键值对可以正常被序列化和反序列化。要使用这个协议,只需要将pickle.dump()中的protocol参数设置为4即可:
import pickle
from collections import OrderedDict
od = OrderedDict([(i, i * i) for i in range(5)])
# 序列化有序字典
with open('od.pkl', 'wb') as f:
pickle.dump(od, f, protocol=4)
# 反序列化有序字典
with open('od.pkl', 'rb') as f:
od_loaded = pickle.load(f)
print(od_loaded)
输出:
OrderedDict([(0, 0), (1, 1), (2, 4), (3, 9), (4, 16)])
2. 添加元素时可以使用update方法的key参数
在Python3.9之前,我们向有序字典中添加元素时只能使用od[key] = value这种语法,或者使用od.update({key: value})。这样做有一个明显的缺点:如果key已经存在,我们并不能像在普通字典中那样获得任何提示。即使是在od.update()中,如果key已经存在,它也会被强制更新,我们也看不到任何提示。
Python3.9引入了一个新的可选参数——key,它可以在添加元素时检查key是否已经存在。如果key已经存在,我们可以选择抛出一个异常、覆盖原有的键值对,或者忽略新的键值对。下面是一个例子:
from collections import OrderedDict
od = OrderedDict([(i, i * i) for i in range(3)])
print(od)
# 插入新元素,不允许重复的key
od.update([(1, 100)], key=lambda x: x[0], existing='raise')
print(od)
# 插入新元素,忽略重复的key
od.update([(1, 100)], key=lambda x: x[0], existing='ignore')
print(od)
# 插入新元素,覆盖重复的key
od.update([(1, 100)], key=lambda x: x[0], existing='overwrite')
print(od)
输出:
OrderedDict([(0, 0), (1, 1), (2, 4)])
OrderedDict([(0, 0), (1, 100), (2, 4)])
OrderedDict([(0, 0), (1, 1), (2, 4)])
OrderedDict([(0, 0), (1, 100), (2, 4)])
总结
有序字典是Python3.7及其之后版本的内置类型。Python3.7引入了一个新的序列化协议,修复了pickle库无法处理序列化有序字典最后一个元素的限制。Python3.9引入了一个新的参数——key,在向有序字典中添加元素时可以检查key是否已经存在。这些小“惊喜”可以帮助我们更方便地使用有序字典。