为什么OrderedDict键视图进行比较时不考虑顺序?

13

为什么 OrderedDict 的键视图是无序的?

>>> from collections import OrderedDict
>>> xy = OrderedDict([('x', None), ('y', None)])
>>> yx = OrderedDict([('y', None), ('x', None)])
>>> xy == yx
False
>>> xy.keys() == yx.keys()
True

有人认为OrderedDict的键视图应该像OrderedSet一样运作,但实际上它的运作方式与dict.keys相同(即像普通的set)。

Python2中也存在相同的“问题”:

>>> xy.viewkeys() == yx.viewkeys()
True

它们是不同的类型,(odict_keysdict_keys的子类)

>>> type(xy.keys())
odict_keys
>>> type({}.keys())
dict_keys

已经存在一个适用于有序键比较的可用方法,但显然只用作odict丰富比较的后检查。这是设计决策还是错误?如果这是设计决策,哪里可以找到对其进行讨论的理由?


1
如果你对这种行为很感兴趣,因为你也需要比较订单,你可以将键值分别提取出来并进行比较:list(xy) == list(yx) - Benjamin Hodgson
1
如果您想在Python3.x上进行稍微懒惰的评估,请使用@BenjaminHodgson或all(x == y for x, y in zip(xy, yx)) - mgilson
3
你需要检查 len 的值,或者使用 itertools.zip_longest函数,并加上一个哨兵(sentinel),否则会出现以下情况:当一个键集合是另一组键集合的截断时,将返回 True。你也可以将更多的工作推到 C 层,通过在顶部导入 from operator import eq 并在测试站点执行 len(xy) == len(yx) and all(map(eq, xy, yx)) 来验证长度,然后尽可能高效地执行 C 级别的键比较,从而使所有但最小运行速度更快。 - ShadowRanger
@ShadowRanger -- 很好的观点。我没有想到,但我应该想到的。 - mgilson
1
还要注意,其他 Python 实现默认使用有序字典(例如pypy)。让KeysView在有序字典和普通字典中表现不同可能会使它们的实现更加困难(这在语言决策中有时会考虑到)。 - mgilson
1
我在Python-ideas邮件列表上发布了一条消息,以获取更多信息:https://mail.python.org/pipermail/python-ideas/2015-December/037472.html - Alexandre
2个回答

7
看起来 OrderedDict 将各种视图对象的实现委托给通用的 dict 实现; 即使在 Python 3.5 中,OrderedDict 获得了 C 加速实现 (它将对象构造委托给 _PyDictView_New,并且没有为通用视图提供覆盖富比较函数)。

基本上,OrderedDict 视图以其支持的 OrderedDict 的相同顺序迭代(因为这样做没有成本),但对于类似于 set 的操作,它们像 set 一样使用内容相等性、子集/超集检查等。

这使得忽略排序的选择在某种程度上是有意义的; 对于一些 set 操作 (例如 &, |, ^), 返回值是一个没有顺序的 set (因为没有 OrderedSet, 即使有,对于像 & 这样的东西,使用哪个排序是不同的?), 如果一些类似于 set 的操作是有序的,而另一些不是,你会得到不一致的行为。当两个 OrderedDict 键视图是有序的时候,但将 OrderedDict 视图与 dict 视图进行比较时却不是这样。

正如我在评论中指出的那样,您可以很容易地通过以下方式获得对键的敏感性:

from operator import eq

# Verify that keys are the same length and same set of values first for speed
# The `all` check then verifies that the known identical keys appear in the
# same order.
xy.keys() == yx.keys() and all(map(eq, xy, yx))

# If you expect equality to occur more often than not, you can save a little
# work in the "are equal" case in exchange for costing a little time in the
# "not even equal ignoring order case" by only checking length, not keys equality:
len(xy) == len(yz) and all(map(eq, xy, yx))

2
但是,比较有序字典视图和字典视图并不一致,这已经存在了这种不一致性,导致了可怕的情况,即 a == b == ca != c,因为 a 和 c 是有序的而 b 不是。当两个 OrderedDict 键视图是顺序敏感的时候,这会变得更加奇怪。无论如何,点赞。 - user2357112
@user2357112:是的,我并没有说理念在所有地方都得到了一致的遵循,只是针对映射视图。 :-) - ShadowRanger

0

我找不到任何已发布的内容,但我猜这个逻辑可以证明这种行为:

如果你有两个字典,d1和d2,你会期望比较它们的键是否相同,对吧?

def compare_dict_keys(d1, d2):
    d1.keys() == d2.keys()

这个函数对于任何类型的字典都应该有相同的行为(OrderedDictdict 的一种类型)。如果这样一个函数开始返回 False,仅仅因为 d1 和 d2 被排序了,那么它似乎是错误的。

换句话说,这些应该都得到相同的结果(而且它们确实是):

>>> {1:2, 3:4}.keys() == {3:4, 1:2}.keys()
True
>>> {1:2, 3:4}.keys() == OrderedDict([(3,4),(1,2)]).keys()
True
>>> OrderedDict([(1,2),(3,4)]).keys() == OrderedDict([(3,4),(1,2)]).keys()
True

但是OrderedDict很特别,不是吗?

OrderedDict提供给你的保证是在迭代时保持顺序。对于OrderedDict.keys()也存在同样的保证,但不会破坏与dict的兼容性。


注意,d1.keys() != d2.keys() 不意味着 d1.keysview() != d2.keysview()(在Python2.x中)。 - mgilson
@mgilson d.keysview() 是什么? - zvone
Argv -- 我总是搞反了。我的意思是 d1.keys() != d2.keys() 不意味着 d1.viewkeys() != d2.viewkeys()。举个具体的例子,考虑 d1 = {1:1, 9:9}d2 = {9:9, 1:1} -- 这里 .keys() 的结果不相等,但这两个无序字典有相同的键。 - mgilson
{1:1, 9:9}{9:9, 1:1}实际上没有区别。Python 2的问题在于dict.keys()比较存在问题(也就是说,在Python 2中你根本不应该做d1.keys() == d2.keys(),因为这样做会自讨苦吃)。 - zvone
@zvone:它并没有出错,只是返回了一个复制字典键的列表;虽然不是出错了,但对性能不利,也不是你通常需要的。 - ShadowRanger
@ShadowRanger 这个比较是有问题的,也就是说,将它们进行比较并不能保证任何结果。换句话说,如果 a.keys() != b.keys(),这并不能告诉你任何事情。实际上,a == b 仍然可能是真的。 - zvone

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