为什么Python 3.7中的字典不可逆?

18

从3.7版本开始,标准Python字典保证维护插入顺序。 (*)

d = {'b': 1, 'a': 2}
for k in d: 
    print(k)
# Prints always 'b' before 'a'.

换句话说,字典键以严格的顺序保留。原则上,这将允许键可逆。然而,以下任何操作都不起作用:

# TypeError: 'dict' object is not reversible
for k in reversed(d): 
    print(k)

# TypeError: 'dict_keys' object is not reversible
for k in reversed(d.keys()): 
    print(k)
问题: 这种行为背后的推理是什么?为什么字典不能被反转?是否有计划在将来更改这种行为?
当然,解决方法有效:
for k in reversed(list(d.keys())): 
    print(k)

(*) 实际上,这已经是Python 3.6的典型安装情况,如此帖所述。


更新: 从Python 3.8开始,字典确实是可逆的。被接受的答案涉及了Guido和其他核心开发人员之间的讨论,这导致了这个决定。简而言之,他们权衡了语言的一致性、实现的努力和实际用户的收益。

3个回答

10

根据文档

reversed(seq)

返回一个反向的迭代器。seq必须是一个对象,它具有__reversed__()方法或支持序列协议(即具有__len__()方法和以0开始的整数参数的__getitem__()方法)。

dict对象并没有实现__reversed__,但它确实实现了后面提到的两个方法。然而,__getitem__方法接受的参数是键,而不是以0开始的整数。

至于为什么这样做,已经在这里进行过建议和讨论。

编辑:

这些引用来自Python-Dev邮件列表(主题“为dict添加__reversed__方法”,于18年5月25日开始),我将从“概念”论点开始,第一个是Antoine Pitrou:

值得注意的是,有序字典(OrderedDict)已经支持了reversed()。 这个论点可以两面说:

  1. 现在的dict类似于有序字典,因此也应该支持reversed()

  2. 您可以使用有序字典来明确表示您关心元素的顺序,并支持reversed()

我的想法是,对于常规字典来说,保证插入顺序是全新的,所以需要一些时间让这个想法在人们的日常思考中定格下来。一旦这种情况发生,可能会出现使用案例,并且__reversed__将在某个时候被添加。实现似乎很简单,预期有限有序集合可逆转并不需要太多概念性的跳跃。

Raymond Hettinger的回复如下:

鉴于字典现在跟踪插入顺序,想要知道最近的插入(即在任务字典中循环遍历最近添加的任务)似乎是合理的。其他可能的用例可能与我们如何使用Unix tail命令相对应。

如果出现这些用例,那么已经支持__reversed__将非常好,这样人们就不会试图使用popitem()调用后跟重新插入的丑陋解决方法了。

邮件列表中表达的主要关注点是,这将在至少一些实现中添加太多膨胀或减少内存效率(必须有双向链表而不是单向链表),以下是Inada Naoki在Python bug tracker (问题33462)中的引用:

“拥有顺序”并不意味着“可逆”。例如,单向链表是有序的,但不可逆。
虽然CPython实现可以提供高效的__reverse__,但添加__reverse__意味着期望所有的Python实现都能提供它。例如,某些Python实现可以使用哈希表+单向链表来实现dict。如果添加了__reverse__,这将不再可能。
回到邮件列表,以下是最后两条消息(均于2018年08月06日发布)。第一条来自Michael Selik:
“我说+1表示同意在v3.8中包含它,这正确吗?”
该线程的最后一个观点是INADA Naoki研究了各种实现,并决定可以在3.8中包含此功能。据我所知,Guido同意INADA的建议,等待MicroPython v3.7的实现。既然INADA改变了想法,我猜这是全部赞成?
最后是Guido van Rossum的消息:
我认为这听起来是正确的。然后我们将拥有两个版本的情况:
- CPython中已实现了保留顺序的3.6,但是在语言规范中; - 3.7中也将其添加到语言规范中。

如其他答案和评论所述,自3.8版本(2018年10月14日)起,reversed()已支持字典和字典视图。


5
你的第二句话似乎是从那个帖子中有偏见地选择出来的。共识似乎是该功能将在3.8版本中添加。此外,在Python 3.7之前,普通的dict对象没有排序(至少不能从语言上保证),因此reversed也没有意义。 - FlyingTeller
我并没有参与其中,只是引用了他的第一次回应。但你说得对,很有道理 - 我已经删除了这个引用。 - gstukelj
1
谢谢。Python-dev的讨论线程很有启示性。事实上,这个功能已经在Python 3.8中实现了,而Python 3.8是在两天前(2019年10月14日)发布的。 - normanius
1
自从Python 3.8版本,reverse()方法可以用于字典。 - Kevin Müller
文档引用并不是很有帮助,它只会引出下一个问题:“那么,为什么字典类型没有实现__reversed__方法呢?”Python开发者的链接有一些有用的内容,但相关部分应该直接在答案中复制(因为这样的外部链接往往会失效)。 - wim
@wim 我保留了原始答案,但我尝试挑选出相关部分进行归档,并将它们添加到答案中。随意编辑问题以添加/删除引用以更好地保留有用的内容。 - gstukelj

3

3

Python 3.8 更新说明

在使用 reversed() 方法时,字典(Dict)和字典视图(dictviews)将按照插入的相反顺序进行迭代。

>>> dict = {1: "1", 2: "2", 3: "3"}
>>> reversed(dict)
<dict_reversekeyiterator object at 0x7f72ca795130>

这个回答是否提供了任何新的内容,而不是其他回答、评论或问题本身已经涵盖的内容? - normanius
1
@normanius 它有一个简短的可视化代码示例,可能对快速浏览器有帮助。 - jamylak
py3.8 TypeError: 'dict'对象不可逆 - CS QGB

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