我在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)]'
答案 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的dict
和set
的交互式输出(普通的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)])