Python:如果键不在字典中,为什么不调用 __eq__()?

Python: Why is __eq__() not invoked if key is not in dictionary?

目前,我正在研究 Python 的内置类型。我很困惑调用什么方法来检查键是否在字典中。例如,如果我检查 int 类型的键是否在字典中,只有当 dictionary.keys() 包含它时才会在后台调用 __eq__() 方法。如果不是,则不调用 __eq__()

这是一个代码示例:

dict = {
  1: "Hello",
  2: "World", 
  4: "Foo"
}

assert 1 in dict.keys() # the __eq__() method of int is invoked
assert not(3 in dict.keys()) # no __eq__() method of int is invoked

我知道,字典包含哈希(键)、键和值的元组。但是我有点困惑,为什么 __eq__() 在第二个断言中没有被调用。

为了测试这个行为,我继承自 int 并设置了一些断点。这是我的习惯摘录 int class:

tint(int):
    def __new__(cls, value, *args, **kw):
        return super(tint, cls).__new__(cls, value)

    def __init__(self, value):
        super().__init__()

    def __eq__(self, other):
        return super().__eq__(other) # with breakpoints

    def __ne__(self, other):
        return super().__ne__(other) # with breakpoints

    def __hash__(self):
        return tint(super().__hash__()) # with breakpoints

我在 Ubuntu 18.04.1 LTS 上使用 Python 版本 3.6.5。

要在字典中找到一个键,首先要通过哈希找到候选者,然后看它是侥幸(尽管哈希相同,但对象不同)还是真的是你想要的键。因此,如果您在该哈希值下找不到任何内容,则没有什么可 eq 的。

想象一下,您要在聚会上找熟人,但不知道她是否在场。她有红色的头发。通常,你会寻找红发女孩,然后去面对她们,看看是不是真的是她。如果还没有红发女孩来,检查聚会上的每一个人是没有意义的。 (假设您的朋友不喜欢日常染色工作。)

编辑:CPython 将字典存储在数组中,其中主数组位置由散列确定;如果那个位置被占用,它会以数学上确定的方式跳到下一个候选位置。由于填充的位置可以因此保存 "correct" 哈希或不相关的哈希,当查找哈希时 CPython 将从主要位置开始,然后继续比较哈希直到它确定不可能搜索-因为钥匙在那里。此时哈希是普通的低级整数,而不是 Python 对象,这解释了为什么哈希比较不会触发 __eq__.

注意源代码中的一个可爱的优化:对于每个候选者,CPython 首先检查对象身份;只有这样它才会检查哈希值是否仍然相同,如果是,则进行慢速检查以查看对象是否相等(使用 PyObject_RichCompareBool,最终调用 __eq__)。为什么这很重要?看这里:

class Foo:
    def __eq__(self, other):
        return False             # This shouldn't match...
    def __hash__(self):
        return 7

f = Foo()
d = { f: "Yes!" }
print(d[f])                      # ...and yet it does! :)
# => "Yes!"