OrderedDict如何知道已经实例化的字典的元素顺序?

时间:2017-07-27 09:46:15

标签: python python-3.x dictionary ipython python-3.6

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

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

我明白了:

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

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

od = OrderedDict(d)

输出是:

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

现在我问自己,OrderedDict - 构造函数如何知道d实例化时元素的顺序?并且它总是表现相同,这样我可以依赖OrderedDict中元素的顺序吗?

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

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)]'

3 个答案:

答案 0 :(得分: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}"

类似地print

我不确定为什么IPython会使用3.6执行此操作,这非常令人困惑(编辑:请参阅相关的issue on GitHub)。在您的标准Python REPL中,由于sys.displayhook未实现任何漂亮的打印,因此不会显示此行为。

您已创建的dict 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 :(得分:7)

在3.6中,作为实现细节,所有dict都是有序的。你被IPython愚弄了:在3.6之前,键的顺序是任意的,所以为了用户友好性,IPython的dictset的交互式输出(普通的Python只打印{{1} })对键进行排序。这就是为什么您的repr似乎按字母顺序排列的原因。在3.6+上运行时,IPython最终可能会放弃这种行为,因为正如你所注意到的那样,这是非常令人困惑的。

如果您明确dict,而不是依靠print为您输出上一个表达式的结果,那么您将绕过ipython的REPL魔法并看到“自然“订单。对于与ipython进行交互的任何其他方式也是如此,因为迭代将按预期的顺序进行。

答案 2 :(得分:4)

您可能知道,Python中的字典不是根据语言规范排序的。它们确实有固有的顺序,但顺序是任意的。

因此,当您将标准字典传递给OrderedDict的构造函数时,将通过迭代其值来从原始字典的值中填充新的OrderedDict。这样,将使用字典的固有顺序,这将是您将在最终OrderedDict中看到的内容。

现在,使用Python 3.6,默认字典的实现发生了变化。正如this question所讨论和解释的那样,标准词典现在保留了插入顺序。这就是为什么当你从Python 3.6 dict创建OrderedDict时,原始顺序也会被保留。

这是否意味着OrderedDict在Python 3.6+中已经过时了?不,因为保留标准词典的顺序是实现细节。新字典恰好具有“正确”的顺序,而不是先前实现的任意顺序。但这并不是语言规范所保证的,并且可能或可能不是其他实现的情况。因此,你不能也不应该依赖它。

顺便说一下。请注意,Python 3.6(语言,而不仅仅是实现)确实保证了OrderedDict的关键字参数的顺序。例如。这保留了订单:

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