有序字典不是有序的吗?

31

我正在尝试使用一个 OrderedDict,但它总是无序创建。例如,

from collections import OrderedDict
OrderedDict(a=1,b=2,c=3)
产生。
OrderedDict([('a', 1), ('c', 3), ('b', 2)])

与预期不同

OrderedDict([('a', 1), ('b', 2), ('c', 3)])
如何确保它按照我想要的顺序创建?

另请参见https://dev59.com/l2ox5IYBdhLWcg3wyHLc和https://dev59.com/ul8d5IYBdhLWcg3wgiX4。 - PM 2Ring
5个回答

56

collections.OrderedDict 能够追踪添加元素的顺序。在循环中使用它是没有问题的:

c = collections.OrderedDict()
for a,b in zip('abc', (1,2,3)):
    c[a] = b

然而,表达式OrderedDict(a=1,b=2,c=3)通过将多个关键字参数传递给其构造函数创建了一个OrderedDict。在Python 2.7中,关键字参数的顺序不能得到保证。如果您想要这样做,您必须转移到实现了PEP 468的Python 3.6,保存函数中 **kwargs 的顺序.
函数定义中**kwargs语法表示解释器应该收集所有不对应其他命名参数的关键字参数。但是,Python不会保留传递这些收集到的关键字参数的顺序。在某些情况下,顺序很重要。本PEP规定应该将收集到的关键字参数作为有序映射暴露在函数体中。

2
哦,我没想到他们会真的记录并保证那种行为。知道了就好。 - ShadowRanger
3
@ShadowRanger - 关于基本的dict类型是否被排序除了CPython实现细节以外还存在一些不确定性,但是,是的,从3.6开始,关键字参数已经正式成为语言的一部分。 - TigerhawkT3
2
@Copperfield - 是的,这将一个参数发送到构造函数,然后以类似于演示循环的方式迭代该参数。该参数永远不会被发送到任意排序的容器中,它是一个序列,因此其顺序是有保证的。 - TigerhawkT3

19

很奇怪它还没有被提到,但是OrderedDict的表示方式告诉你如何创建它以保持顺序:

OrderedDict([('a', 1), ('b', 2), ('c', 3)])

这种表示方式并非用作障碍物 - 原因是该表示方法可用于创建一个完全相同有序的OrderedDict


仅为了完整性(已经提到过),顺序会丢失,因为OrderedDict(a=1, b=2, c=3)将这些参数捕获为**kwargs,这是一个普通的无序字典。直到Python 3.6推出并做出承诺,即在像您一样传递它时,kwargs的顺序将被保留:

Python 3.6 中的新功能

PEP 468:保留关键字参数排序

函数签名中的**kwargs现在保证是一个保持插入顺序的映射。


3
值得一提的是,原链接中的内容指出Python 3.6中的普通dict现在维护插入顺序,但目前应该将此视为"实现细节,不应依赖此行为(这可能会在未来更改),但希望在修改语言规范以强制保留所有当前和未来Python实现的顺序语义之前,让这个新的字典实现在语言中运行几个版本。" - PM 2Ring
@PM2Ring实际上,在3.6版本中的“保留关键字参数顺序”并不是一项实现细节。所有其他的dict在3.6版本中有序是一项实现细节。:-) 不过在帖子下加上那条评论是很好的,它是一条重要的信息。谢谢。 - MSeifert
@同意!在我之前的评论中,我并没有暗示保留关键字参数顺序是一个实现细节。但是当然,让普通字典保持插入顺序确实使得保留关键字参数顺序变得更加容易。 :) - PM 2Ring

13

阅读文档

OrderedDict构造函数和update()方法都接受关键字参数,但它们的顺序会丢失,因为Python的函数调用语法使用常规无序字典来传递关键字参数。

您必须将输入作为元组序列(或现有的有序字典类型)传递以保留顺序。

请注意,Python 3.6现在提供了一个保证,即关键字参数按它们在代码中出现的顺序(从左到右)传递,感谢PEP 468,因此在Python 3.6及更高版本上,您的代码将正常工作。


1
Python 3.6 中的有序保证 - Antti Haapala -- Слава Україні

3
这是因为传递的关键字参数 (variable = value, ) 会首先被合并成一个Python字典,而Python字典是无序的。正如您可以在 Init 签名中看到的那样,kwds 就是这个字典。
Init signature: OrderedDict(self, *args, **kwds)

这是当你传递关键字参数时,OrderedDict 内部将如何初始化:
for key, value in kwds.items():
   self[key] = value

由于kwds是无序的,你将得到一个无序的OrderedDict。

可以这样创建有序字典:

from collections import OrderedDict
from string import ascii_lowercase

d = OrderedDict()
for a,b in enumerate(ascii_lowercase[:3], 1):
    d[b] = a

或者:

n=3
d = OrderedDict(zip(ascii_lowercase[:n], range(1,n+1))) 
print d 

输出:

OrderedDict([('a', 1), ('b', 2), ('c', 3)])

好的,但是如何创建所需的 OrderedDict 呢? - wogsland
@wogsland 应该使用 ascii_lowercase 吗? :) - Mohammad Yusuf

1
你可以使用 sorted() 创建所需的映射:
dict = {"a":"some_value", "b":"other_value", "c":"foo"}
ordered = OrderedDict(sorted(dict.items(), key=lambda t: t[0]))

这将在将项目传递给OrderedDict构造函数之前对其进行排序。 key=部分设置了排序方式,t[0]按字典键排序。

3
关键字"key="是不必要的-仅按元组(key, value)排序将自动得到一个按键排序的字典。 - xorsyst

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