如何正确使用OrderedDict构造函数初始化以保持初始数据顺序?
怎样正确地初始化一个有序字典(OrderedDict),以便它能保持初始数据的顺序呢?
from collections import OrderedDict
# Obviously wrong because regular dict loses order
d = OrderedDict({'b':2, 'a':1})
# An OD is represented by a list of tuples, so would this work?
d = OrderedDict([('b',2), ('a', 1)])
# What about using a list comprehension, will 'd' preserve the order of 'l'
l = ['b', 'a', 'c', 'aa']
d = OrderedDict([(i,i) for i in l])
问题:
一个
OrderedDict
能否保持在初始化时传入的元组列表、元组的元组、列表的元组或列表的列表等的顺序?(上面的第二和第三个例子)怎么验证
OrderedDict
确实保持了顺序?因为普通的dict
是没有固定顺序的,如果我的测试数据恰好和dict
的随机顺序一致,那我就可能错误地认为顺序被保持了。比如,如果我写d = OrderedDict({'b':2, 'a':1})
,而不是d = OrderedDict({'a':1, 'b':2})
,我可能会错误地得出结论说顺序是被保持的。在这种情况下,我发现dict
是按字母顺序排列的,但这并不总是如此。有没有什么可靠的方法来用反例验证一个数据结构是否保持顺序,而不是一遍又一遍地尝试测试数据,直到其中一个失败为止?
附注:我在这里留个链接供参考:“OrderedDict 的构造函数和 update() 方法都接受关键字参数,但它们的顺序会丢失,因为 Python 的函数调用语义是通过一个普通的无序字典来传递关键字参数的。”
再附注:希望将来 OrderedDict 也能保持关键字参数的顺序(例子 1):http://bugs.python.org/issue16991
3 个回答
使用生成器表达式也是一种可能的方法,而且效率稍微高一些:
d = OrderedDict((i, i) for i in l)
显然,在这个简单的例子中,使用l
的好处不大,但如果l
是一个迭代器,或者是从生成器中获取结果,比如用来解析和遍历一个大文件,那么这种差别就会非常明显(例如,可以避免将整个内容加载到内存中)。举个例子:
def mygen(filepath):
with open(filepath, 'r') as f:
for line in f:
yield [int(field) for field line.split()]
d = OrderedDict((i, sum(numbers)) for i, numbers in enumerate(mygen(filepath)))
# An OD is represented by a list of tuples, so would this work?
d = OrderedDict([('b', 2), ('a', 1)])
是的,这样做是可以的。根据定义,列表的顺序是固定的,也就是它被表示的方式。这同样适用于列表推导式,生成的列表会按照数据提供的方式来排列(也就是说,如果数据来自一个列表,它的顺序是确定的;但如果数据来自一个集合(set
)或字典(dict
),就不一定了)。
那么,如何验证一个
OrderedDict
确实保持了顺序呢?因为字典的顺序是不可预测的,如果我的测试数据恰好和字典的随机顺序一致,那我该怎么办?比如,如果我写的是d = OrderedDict({'b':2, 'a':1})
,而不是d = OrderedDict({'a':1, 'b':2})
,我可能会错误地得出顺序被保持的结论。在这种情况下,我发现字典的顺序是按字母顺序排列的,但这并不总是成立。那么,有什么可靠的方法来用反例验证一个数据结构是否保持顺序,而不是不断尝试测试数据直到发现问题呢?
你可以保留你的2元组源列表作为参考,并在进行单元测试时用它作为测试数据。遍历这些数据,确保顺序得以保持。
OrderedDict会保留它所能访问到的任何顺序。要初始化它并传入有序数据,唯一的方法就是传入一个包含键值对的列表(或者更一般地说,是一个可迭代的对象),就像你最后两个例子中那样。正如你提到的文档所说,当你传入关键字参数或字典参数时,OrderedDict并无法获取任何顺序,因为在OrderedDict构造函数看到这些数据之前,顺序就已经被移除了。
注意,在你最后的例子中使用列表推导式并没有改变任何东西。OrderedDict([(i,i) for i in l])
和OrderedDict([('b', 'b'), ('a', 'a'), ('c', 'c'), ('aa', 'aa')])
之间没有区别。列表推导式会被计算出来,生成一个列表,然后这个列表被传入;OrderedDict并不知道这个列表是怎么生成的。