如何保持字典中键/值的声明顺序?

400

我有一个字典,我按特定顺序声明了它,想要一直保持这个顺序。键/值不能根据它们的值进行排序,我只是想按照我声明它的顺序排列。

所以如果我有以下字典:

d = {'ac': 33, 'gw': 20, 'ap': 102, 'za': 321, 'bs': 10}

如果我查看它或遍历它,它不是按照那个顺序排列的。有没有办法确保Python会保持我声明键/值对的显式顺序?


使用Python 3.7或更高版本将有助于保留排序。 - Nam G VU
13个回答

377
自 Python 3.6 起,标准的 dict 类型默认按插入顺序维护元素。
定义:
d = {'ac':33, 'gw':20, 'ap':102, 'za':321, 'bs':10}

将会产生一个字典,其中的键按照源代码中列出的顺序排列。
使用整数数组作为稀疏哈希表,这样可以索引到另一个存储键值对(加上计算出的哈希值)的数组。后者恰好以插入顺序存储项目,整个组合实际上比 Python 3.5 及之前版本使用的实现占用更少的内存。有关详细信息,请参见 Raymond Hettinger 的原始想法帖子
在 3.6 中,这仍被视为一项实现细节;请参见Python 3.6 新特性文档
这个新实现的保序性被认为是一个实现细节,不应该依赖它(这可能会在未来改变,但是希望在将语言规范强制要求所有当前和未来的Python实现保留顺序语义之前,在语言中保留此新字典实现几个版本;这也有助于保持向后兼容性,其中随机迭代顺序仍然生效,例如Python 3.5)。

Python 3.7将此实现细节提升为语言规范,因此在与该版本或更高版本兼容的所有Python实现中,dict现在必须保留顺序。请参阅BDFL的声明。从Python 3.8开始,字典还支持反向迭代

在某些情况下,您可能仍然希望使用collections.OrderedDict(),因为它在标准dict类型之上提供了一些额外的功能。例如可以反转(这也适用于view对象),并支持重新排序(通过move_to_end()方法)。


1
这个语言规范的特性在官方文档中有提到吗?(我找不到) - Manuel Selva
5
@ManuelSelva提到了Python 3.7 的新功能文档,其中提到了这一点(链接到我引用的电子邮件)。字典视图对象部分详细记录了迭代顺序(在iter(dictview)下):键和值按插入顺序迭代字典顺序保证是插入顺序 - Martijn Pieters
2
@ManuelSelva 数据模型文档中的标准类型层次结构部分也涵盖了这个主题(字典保留插入顺序,这意味着键将按照它们按顺序添加到字典中的方式产生)。 - Martijn Pieters
3
惯例是:除非明确说明某些内容是CPython的实现细节,否则文档中标准类型特性的描述会将该特性视为语言规范的一部分。 - Martijn Pieters

182
from collections import OrderedDict
OrderedDict((word, True) for word in words)
包含
OrderedDict([('He', True), ('will', True), ('be', True), ('the', True), ('winner', True)])

如果值是True(或任何其他不可变对象),您还可以使用:

OrderedDict.fromkeys(words, True)

3
当然值得注意的是,“不可变”部分并不是Python会强制执行的硬性规定,它只是一个“好主意”。 - lvc
12
请注意,诸如OrderedDict(FUTURE=[], TODAY=[], PAST=[])的解决方案不起作用,而提到的方法OrderedDict([('FUTURE',[]),('TODAY',[]),('PAST',[])])将保持顺序。 - andilabs
2
@andi 我遇到了另一个问题,在使用jsonify时,有序字典似乎在生成json数据时丢失了它的顺序。有没有什么解决办法? - user4985526
https://github.com/pallets/flask/issues/974 这可以用来解决问题。 - user4985526
8
Python 3.7 现在默认使用有序字典。https://mail.python.org/pipermail/python-dev/2017-December/151283.html - sertsedat
显示剩余2条评论

177

不解释理论部分,我来举一个简单的例子。

>>> from collections import OrderedDict
>>> my_dictionary=OrderedDict()
>>> my_dictionary['foo']=3
>>> my_dictionary['aol']=1
>>> my_dictionary
OrderedDict([('foo', 3), ('aol', 1)])
>>> dict(my_dictionary)
{'foo': 3, 'aol': 1}

18
有没有一种类似于Dict类型的批量赋值OrderedDict的方法? - user4985526
3
OrderedDict确实解决了这个问题,但在这个特定的例子中,使用标准字典可以得到完全相同的结果。 - Tonechas
2
@Tonechas:我刚刚尝试了使用标准字典的示例,并得到了 {'aol': 1, 'foo': 3},所以我认为这是一个很好的说明性示例。 - twasbrillig
4
对于每个人,这里有一个教训:发现 Python 的可预测哈希可能会引起安全漏洞(我想大约是在 2.4 版本左右),因此现在不能保证即使相同代码的两次运行在标准字典中都会给出相同的顺序。 - holdenweb
为什么我们不能传递我们想要用我们的值进行批量初始化的某种格式的顺序?而不是在每一行中分配一个值?(Python2.7) - JavaSa
1
@tyan,你可以使用包含键值对的可迭代对象调用OrderedDict.update()方法:d1.upate([(key1, val1), (key2, val2)]) - Ruud Althuizen

42
注意,此答案仅适用于python3.7之前的版本。CPython 3.6在大多数情况下保持插入顺序,作为一种实现细节。从Python3.7开始,宣布实现必须保持插入顺序才能符合规范。

Python字典是无序的。 如果您想要一个有序的字典,请尝试使用collections.OrderedDict

请注意,OrderedDict是在python 2.7中引入到标准库中的。 如果您有较旧版本的python,则可以在ActiveState上找到有序字典的配方。


1
从Python 3.6开始,字典支持插入顺序。请参见@martijn上面的帖子。 - mrtpk

12

字典会使用一种使搜索更高效的顺序,您无法更改它。

您可以使用对象列表(在简单情况下是一个 2 元素元组,甚至是一个类),并将项目附加到末尾。之后,您可以使用线性搜索在其中查找项目。

或者,您可以创建或使用意图维护顺序的不同数据结构。


字典将使用一种顺序,使得搜索更加高效。终于有人指出了这一点。 - scharette

7

使用字典无法实现您想要的功能。您已经创建了字典 d = {'ac':33, 'gw':20, 'ap':102, 'za':321, 'bs':10},一旦创建后,它就没有保持顺序的方法。我的解决方法是创建一个json文件,其中包含以下对象:

{"ac":33,"gw":20,"ap":102,"za":321,"bs":10}

我使用了:

r = json.load(open('file.json'), object_pairs_hook=OrderedDict)

然后使用:
print json.dumps(r)

验证。


1
那么为什么不从列表开始使用OrderedDict呢?JSON文件在这里并没有真正添加任何内容。 - Martijn Pieters
是的,列表更有用以保持顺序,但回答是关于有关有序字典的问题。只是让人们了解使用字典的限制,并为他们提供可能的解决方法,如果他们需要使用字典的话。 - nealous3

7
我在尝试弄清楚如何使用OrderedDict时遇到了这篇文章。在Eclipse的PyDev中,无法找到OrderedDict,因此我最终决定将字典键值的元组作为我想要排序的方式。当我需要输出我的列表时,我只需迭代元组的值,并将元组的迭代“键”插入字典中以按所需顺序检索我的值。
例如:
test_dict = dict( val1 = "hi", val2 = "bye", val3 = "huh?", val4 = "what....")
test_tuple = ( 'val1', 'val2', 'val3', 'val4')
for key in test_tuple: print(test_dict[key])

这个方法有点繁琐,但是我时间紧迫,这是我想到的解决办法。

注意:其他人建议的列表套列表方法对我来说并不太合适,因为列表是有序且索引的(而且也与字典结构不同)。


很棒的解决方案。我将使用它来将JSON写入文件,始终按照相同的顺序。 - Hrvoje T

5
from collections import OrderedDict
list1 = ['k1', 'k2']
list2 = ['v1', 'v2']
new_ordered_dict = OrderedDict(zip(list1, list2))
print new_ordered_dict
# OrderedDict([('k1', 'v1'), ('k2', 'v2')])

1
主要问题不再是字典,而是元组列表。 - Yu Da Chi

2

在开发Django项目时,我遇到了类似的问题。由于我使用的是旧版本的python,无法使用OrderedDict,所以解决方案是使用Django的SortedDict类:

https://code.djangoproject.com/wiki/SortedDict

例如:

from django.utils.datastructures import SortedDict
d2 = SortedDict()
d2['b'] = 1
d2['a'] = 2
d2['c'] = 3

注意:此答案最初发布于2011年。如果您可以访问Python 2.7或更高版本,则应该可以访问现在标准的collections.OrderedDict,其他人在本帖中提供了许多示例。

2
另一个选择是使用 Pandas dataframe,因为它保证了字典结构中项目的顺序和索引位置。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接