【问题标题】:Python: Why is __eq__() not invoked if key is not in dictionary?Python:如果键不在字典中,为什么不调用 __eq__()?
【发布时间】:2019-02-01 21:08:57
【问题描述】:

目前,我对 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 类的摘录:

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。

【问题讨论】:

    标签: python python-3.x hashmap built-in


    【解决方案1】:

    要在字典中查找键,首先通过哈希找到候选者,然后查看它是侥幸(即使哈希相同,对象也不同)还是您真正想要的键。因此,如果您在该哈希下找不到任何内容,则没有什么可以用的。

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

    编辑:CPython 将字典存储在一个数组中,其中主数组位置由哈希确定;如果该位置被占用,它会以数学上确定的方式跳到下一个候选位置。由于填充的位置因此可以保存“正确”哈希或不相关的哈希,因此在查找哈希时,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!"
    

    【讨论】:

    • 这听起来很明显。但是,哈希也是int。那么为什么没有针对该哈希值(int)的__eq__() 调用?我认为您必须比较哈希值。
    • 哈希是int,是的。不是tint(尽管您尝试过)。
    • 查看sourcehash 始终是Py_hash_t,而不是PyObject。看着here,“这是一个与 Py_ssize_t 大小相同的有符号整数。” (另请注意,它们与普通 C == 进行比较,而不是通过调用 Python 的 __eq__。)您返回 tint,但所有 CPython 听到的只是 C int
    • 换句话说,哈希码在 C 代码内部进行比较。 C 代码只处理原始机器整数,而不是 Python 对象。
    猜你喜欢
    • 1970-01-01
    • 2011-01-17
    • 2023-03-13
    • 1970-01-01
    • 2013-04-20
    • 2015-12-07
    • 2011-06-09
    • 2013-03-04
    • 2014-01-13
    相关资源
    最近更新 更多