Python字典视图对象的等价性

3

对于 Python dict 的 View 对象,比我预期的比较方式不同:

a = {0 : 'a', 1 : 'b'}
b = {1 : 'b', 0 : 'a'}

print(a == b) # True
print(a.keys() == b.keys()) # True
print(a.values() == b.values()) # False
print(a.items() == b.items()) # True

dict.values() 返回的结果不是列表,而是一个“视图”对象。这意味着该对象在被调用时会动态更新,以反映底层字典中键值的更改。因此,在同一字典上多次调用 dict.values() 会返回具有相同内容的不同视图对象,它们之间的比较结果为 False。

实际上,在相同的字典(甚至不是副本)的情况下,也是如此。

a = {0 : 'a', 1 : 'b'}
print(a.values() == a.values()) # False

这个视图对象的相等性意味着什么?

3个回答

2
字典视图类实现了自己的等式方法。 dict.values() 的文档明确指出:
将一个 dict.values() 视图与另一个进行等式比较将始终返回 False。当将 dict.values() 与其自身进行比较时,也适用此规则。
对于 dict.keys()dict.items()字典视图 的文档说:
键视图是集合样式的,因为它们的条目是唯一且可哈希的。如果所有值都是可哈希的,以便 (key, value) 对是唯一且可哈希的,则项目视图也是集合样式的。(值视图通常不被视为集合样式,因为条目通常不是唯一的。)对于集合样式的视图,可以使用定义在抽象基类 collections.abc.Set 中的所有操作(例如,==<^)。

谢谢!我错过了那个。你有什么见解为什么它必须返回False吗?看起来这有点浪费一个好的运算符... - tungli
1
为了避免像对所有值进行排序这样昂贵的操作以便进行比较。 - Barmar
keysitems可以像集合一样处理,因为键保证是唯一且可哈希的。但是值可以有重复。 - Barmar

2

keysitems视图类似于集合(对于具有不可哈希值的items视图,它们大多数表现为集合)-它们在许多方面的行为类似于set对象,特别是可以很容易地在这些视图上执行in测试。这样,这些视图支持基于两个视图是否包含相同元素的有效的==操作。

对于values视图,没有好的方法来实现这种==操作,因此values视图不会为==实现任何花哨的东西。它们只继承了object的默认__eq__实现,因此仅当两个values视图是相同对象时,它们才被视为相等。即使对于同一字典的两个视图,只有当它们实际上是相同的视图对象时,您才会得到True

In [2]: x = {}

In [3]: x.values() == x.values()
Out[3]: False

In [4]: v = x.values()

In [5]: v == v
Out[5]: True

谢谢你的回答! “没有好的方法来实现这样的==操作…” -> 使用以下代码有什么问题吗?:def __eq__(self, other): return all(i == j for i, j in zip(self, other))? - tungli
2
@tungli 这需要两个字典的元素顺序相同。 - Barmar
1
@tungli:这会将元素顺序带入比较中,导致相等的字典具有不相等的值视图。这与字典比较和所有其他视图比较的工作方式不一致。(另外,您忘记先检查len了。) - user2357112
此外,这些设计决策是在dict成为有序之前进行的。 - user2357112
不确定这是否重要,因为这些运算符都不关心顺序。也许实现值相等的最佳方法是类似于 Collections.counter(self) == Collections.counter(other) 这样的方式。 - Barmar
计数器需要可哈希的元素。 - wim

0

dict是无序的,这意味着只要a中的所有键存在于b中,b中的所有键存在于a中,并且这些键对应的值相等,那么这两个集合就是相等的。

如果你想检查ab是否是同一个实例,那么你可以使用is

>>> a = {0 : 'a', 1 : 'b'}
>>> b = {1 : 'b', 0 : 'a'}

>>> a is b
False

>>> a = {0 : 'a', 1 : 'b'}
>>> b = a

>>> a is b
True

关于为什么 a.values() == b.values() 返回 False 的问题,这是 Python 语言中做出的选择。引用自 Python 文档的一句话:

An equality comparison between one dict.values() view and another will always return False. This also applies when comparing dict.values() to itself:

>>> d = {'a': 1}
>>> d.values() == d.values()
False

这并不能解释为什么 keys()items() 对象是相等的,但 values() 对象却不相等。 - Barmar
正如@Barmar所说,这是错失了重点。如果例如a.values() is a.values()返回False,我可以理解,但使用==就很奇怪。 - tungli
另外,dict现在是插入有序的(自3.7开始) - tungli
我更新了答案以回答那个问题。 - Stephen
那个文档部分不太正确 - 在values视图上使用==可能会返回True,但仅当您将同一视图对象与自身进行比较时才会如此。调用两次values会返回两个不同的视图对象,因此您会得到d.values() == d.values()False - user2357112
文档需要更清晰明了,我同意,因为它们似乎自相矛盾。它声明每次调用都会返回一个新视图,并且因此导致在两个不同视图之间进行比较,应该返回 False。但是不知道为什么将其与自身进行比较会返回 True - Stephen

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