如何让有序字典了解已经实例化字典的元素顺序?

16

我在 Python 3.6 中试着使用 OrderedDict 类型,并对其行为感到惊讶。当我在 IPython 中创建一个简单的 dict ,像这样:

d = dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])

我得到:

{'guido': 4127, 'jack': 4098, 'sape': 4139}

作为输出,由于某些原因,它不保留元素实例化时的顺序。现在,当我像这样从 d 创建一个 OrderedDict

od = OrderedDict(d)

输出结果为:

OrderedDict([('sape', 4139), ('guido', 4127), ('jack', 4098)])

现在我自问,OrderedDict构造器如何在实例化d时知道元素的顺序?它是否总是表现一致,这样我就可以依赖于OrderedDict中的元素顺序了吗?

我已经阅读了关于Python字典和OrderedDict的文档,但我没有找到我的问题的答案。

(sys.version)的输出:

In[22]: sys.version
Out[22]: '3.6.1 (default, Apr  4 2017, 09:40:21) \n[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.38)]'

2
请参见以下链接:https://dev59.com/6VkS5IYBdhLWcg3wXFg9 - Alasdair
3
这里有些奇怪的事情发生了。在 Python 3.6 中,dict([('sape', 4139), ('guido', 4127), ('jack', 4098)]) 不应该得出那样的输出结果。 - Dimitris Fasarakis Hilliard
1
你也在用IPython,看起来有些古怪的事情正在发生。试着在REPL中测试一下,看看能否重现它。 - Dimitris Fasarakis Hilliard
2
@JimFasarakisHilliard 我想这只是强调不应该依赖于实现细节。也许应该编辑并重新打开问题。 - Chris_Rands
1
IPython只是以这种方式打印字典吗?如果您在.items()上进行迭代,顺序是否得到保留? - Thierry Lathuille
显示剩余13条评论
3个回答

12
现在很明显,IPython用于显示输出的自定义钩子(sys.displayhook)正在使用它自己的漂亮打印机( using it's own pretty printer )。通过直接调用displayhook,您可以看到它如何破坏插入顺序:
In [1]: from sys import displayhook
   ...: displayhook({'1': 0, '0': 1})
Out[1]: {'0': 1, '1': 0}

此外,如果您选择使用字典 str(发送一个字符串以代替字典对象进行显示),则会得到正确和预期的顺序:

In [2]: d = dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
   ...: d
Out[2]: {'guido': 4127, 'jack': 4098, 'sape': 4139}

In [3]: str(dict(t))
Out[3]: "{'sape': 4139, 'guido': 4127, 'jack': 4098}"

同样地,通过打印它来实现。
我不确定为什么IPython在3.6版本中会这样做,这是相当令人困惑的(编辑:请参见GitHub上相关的问题)。在您的标准Python REPL中,此行为不会出现,因为sys.displayhook没有实现任何漂亮的打印。
你创建的字典d确实保持了插入顺序,这就是为什么OrderedDict也保持了相同的顺序。
当然,它保持顺序的事实是一种实现细节。在这个细节发生变化之前(似乎会发生),你应该坚持使用OrderedDict来可靠地保持跨实现的顺序。

顺便提一下,如果你想禁用它,可以使用--no-pprint选项启动IPython,这将禁用其漂亮打印功能:

➜ ipython --no-banner --no-pprint 

In [1]: dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
Out[1]: {'sape': 4139, 'guido': 4127, 'jack': 4098}

1
以下是相关的IPython错误报告:https://github.com/ipython/ipython/issues/10110。 基本上,他们认为Python 3.6保持字典顺序的事实是一种实现细节,不应该依赖于它,这至少有一定的道理。 - Sven Marnach

7
在3.6中,作为实现细节,所有的dict都是有序的。你被IPython欺骗了:在3.6之前,键的顺序是任意的,所以为了用户友好性,IPython对dictset的交互式输出(正常Python只会打印repr)进行排序。这就是为什么你的dict看起来是按字母顺序排列的原因。当你显式地print时,而不是依赖于ipython来输出上一个表达式的结果,你将绕过ipython的REPL魔法,并看到“自然”的顺序。对于与dict的交互,几乎所有其他方式都适用,因为迭代将按预期的顺序进行。

IPython交互式输出是否像您所述的那样对“set” s进行排序? 在Python 3中,pprint不会对其进行排序。https://dev59.com/YVcO5IYBdhLWcg3w-V1X - Chris_Rands
@Chris_Rands: 有区别。在我的当前运行环境(Linux x64 Py3.5.2,IPython 5.1.0)中,repr({'a', 'bbb', 'cde', 'ffe'}) 的输出结果为 "{'cde','a','bbb','ffe'}",但 REPL 输出结果为 {'a','bbb','cde','ffe'} - ShadowRanger
好的,谢谢。Jim 的答案表明,在 IPython 中 dict 的这种行为的根本原因是 pprint,所以我只是想 set 可能也会以同样的方式工作,但显然并不是这样。 - Chris_Rands
1
@Chris_Rands Jim的答案不是在谈论标准库的pprint.pprint,而是在谈论ipython的pprint选项。 - PM 2Ring
@PM2Ring 谢谢,我不知道IPython的pprintpprint.pprint的行为是不同的。 - Chris_Rands
1
@Chris_Rands 我对IPython几乎一无所知,但我认为他们在REPL中使用自己的漂亮打印器而不是几乎无法配置的pprint.pprint - PM 2Ring

4
如你所知,Python中的字典并不按照语言规范排序。它们确实有一个内在的顺序,但这个顺序是任意的。
因此,当你将标准字典传递给一个OrderedDict的构造函数时,新的OrderedDict将通过迭代其值来填充原始字典的值。这样,字典的内在顺序将被使用,并且这将是你在最终OrderedDict中看到的内容。
现在,随着Python 3.6的推出,默认字典的实现发生了变化。正如在这个问题上讨论和解释的那样,标准字典现在保留插入顺序。这就是为什么当你从Python 3.6字典创建OrderedDict时,原始顺序也被保留。
这是否意味着OrderedDict在Python 3.6+中已经过时了?不是的,因为标准字典保留顺序只是一种实现细节。新字典只是碰巧具有“正确”的顺序,而不是以前实现的任意顺序。但这在语言规范中没有任何保证,其他实现可能或可能不是这种情况。因此,你不能也不应该依赖它。
顺便说一下,注意Python 3.6(不仅仅是实现)保证OrderedDict的关键字参数的顺序被保留。例如,这将保留顺序:
>>> OrderedDict(sape=4139, guido=4127, jack=4098)
OrderedDict([('sape', 4139), ('guido', 4127), ('jack', 4098)])

因此,您不能且不应该依赖它。这就是著名的Python口号,“使用该死的文档,Luke”;-) - Steve Jessop

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