为什么 OrderedDict 的值不相等?

Why are the values of an OrderedDict not equal?

与Python3:

>>> from collections import OrderedDict
>>> d1 = OrderedDict([('foo', 'bar')])
>>> d2 = OrderedDict([('foo', 'bar')])

我想检查是否相等:

>>> d1 == d2
True
>>> d1.keys() == d2.keys()
True

但是:

>>> d1.values() == d2.values()
False

你知道为什么值不相等吗?

我已经用 Python 3.4 和 3.5 测试过了。


在这个问题之后,我在 Python-Ideas 邮件列表上发布了更多详细信息:

https://mail.python.org/pipermail/python-ideas/2015-December/037472.html

在python3中,d1.values()d2.values()collections.abc.ValuesView个对象:

>>> d1.values()
ValuesView(OrderedDict([('foo', 'bar')]))

不要将它们作为对象进行比较,将它们转换为列表然后进行比较:

>>> list(d1.values()) == list(d2.values())
True

调查为什么它可以用于比较键,在 CPython 的 _collections_abc.py 中,KeysView 是从 Set 继承的,而 ValuesView 不是:

class KeysView(MappingView, Set):

class ValuesView(MappingView):
  • ValuesView 及其父代中追踪 __eq__

    MappingView ==> Sized ==> ABCMeta ==> type ==> object.

    __eq__ 仅在 object 中实现,未被覆盖。

  • 另一方面,KeysView直接从Set继承__eq__

在Python3中,dict.keys()dict.values()return特殊可迭代类——分别是一个collections.abc.KeysView和一个collections.abc.ValuesView.第一个从 set 继承它的 __eq__ 方法,第二个使用默认的 object.__eq__ 来测试对象身份。

不幸的是,当前的两个答案都没有说明为什么会这样,而是着重于如何做到这一点。邮件列表的讨论很棒,所以我总结一下:

对于 odict.keys/dict.keysodict.items/dict.items

  • odict.keys (subclass of dict.keys) 支持比较,因为它符合 collections.abc.Set(它是一个类似集合的对象)。这是可能的,因为字典中的 keys(有序或非有序)保证是唯一且可哈希的。
  • odict.items (subclass of dict.items) 也支持比较,原因与 .keys 相同。 itemsview 被允许这样做,因为如果 item 之一(具体来说,表示值的第二个元素)不可哈希,它会引发适当的错误,但可以保证唯一性(由于 keys 独一无二):

    >>> od = OrderedDict({'a': []})
    >>> set() & od.items()
    TypeErrorTraceback (most recent call last)
    <ipython-input-41-a5ec053d0eda> in <module>()
    ----> 1 set() & od.items()
    
    TypeError: unhashable type: 'list'
    

    对于这两个视图 keysitems,比较使用一个名为 all_contained_in(相当可读)的简单函数,该函数使用对象 __contain__ 方法来检查涉及的视图中元素的成员资格。

现在,大约 odict.values/dict.values:

  • 如前所述,odict.values (subclass of dict.values [令人震惊]) 不像集合对象那样进行比较。这是因为一个valuesviewvalues不能表示为一个集合,原因有两个:

    1. 最重要的是,视图可能包含无法删除的重复项。
    2. 视图可能包含不可散列的对象(它本身不足以不将视图视为类似集合的对象)。

正如 @user2357112 and by @abarnett 在邮件列表中的评论中所述,odict.values/dict.values 是一个多重集,是集合的泛化,允许其元素的多个实例。 由于固有的重复、排序以及您可能需要考虑与这些值对应的键这一事实,尝试比较这些并不像比较 keysitems 那样微不足道。应该 dict_values 看起来像这样:

>>> {1:1, 2:1, 3:2}.values()
dict_values([1, 1, 2])
>>> {1:1, 2:1, 10:2}.values()
dict_values([1, 1, 2])

实际上是相等的,即使对应于键的值不一样?可能是?也许不吧?这两种方式都不是直截了当的,并且会导致不可避免的混乱。

不过要指出的是,将它们与 keysitems 进行比较并非微不足道,总而言之,@abarnett 在 [=49 上的另一条评论=]:

If you're thinking we could define what multisets should do, despite not having a standard multiset type or an ABC for them, and apply that to values views, the next question is how to do that in better than quadratic time for non-hashable values. (And you can't assume ordering here, either.) Would having a values view hang for 30 seconds and then come back with the answer you intuitively wanted instead of giving the wrong answer in 20 millis be an improvement? (Either way, you're going to learn the same lesson: don't compare values views. I'd rather learn that in 20 millis.)