【问题标题】:Membership testing with hash collision for sets in pythonpython中集合的哈希冲突成员资格测试
【发布时间】:2021-10-10 14:11:34
【问题描述】:

这是我的课程,我故意让它发生哈希冲突:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f'({self.name} - {self.age})'

    def __eq__(self, other):
        print('__eq__ called for', self)
        return False

    def __hash__(self):
        print('__hash__ called for', self)
        return 1


obj1 = Person('Soroush', 25)
obj2 = Person('John', 23)
print('\n--------Creation of set---------\n')
s = {obj1, obj2}
print(s)
print('\n-------Membership testing-------\n')
print(obj2 in s)

输出:

--------Creation of set---------

__hash__ called for (Soroush - 25)
__hash__ called for (John - 23)
__eq__ called for (Soroush - 25)
{(Soroush - 25), (John - 23)}

-------Membership testing-------

__hash__ called for (John - 23)
__eq__ called for (Soroush - 25)
True

*我的问题来自输出的“成员资格测试”部分:

obj2 in s语句中,python首先计算obj2的hash(好,第一行输出),然后因为obj1obj2有相同的hash,python会去看看他们是不是相同的项目与否,obj1__eq__ 被调用(输出的第二行)。他们不是 !我有意为所有实例返回False。所以 python 继续检查探测序列。这里的哈希值再次相等!为什么 python 不调用 obj2__eq__ 来查看这些项目是否相等?

我希望在第三行看到__eq__ called for (John - 23),而且因为obj2 不等于obj2,所以输出的最后一行应该是False。喜欢:

print(obj2 == obj2)

输出:

__eq__ called for (John - 23)
False

那么,为什么没有__eq__ called for (John - 23),为什么没有True?它们不是同一个项目(同样的事情发生在一秒钟前obj1)。

【问题讨论】:

标签: python hash set


【解决方案1】:

所有内置容器类型在测试(可能很昂贵)对象相等性之前检查对象身份。这种行为是documented

对于容器类型,例如 list、tuple、set、frozenset、dict 或 collections.deque,y中的表达式x等价于any(x is e or x == e for e in y)

当然,基于散列的容器实际上并不是这样检查成员资格的。同样,这是一种优化,可以省去潜在的昂贵的相等操作,考虑检查两个大字符串的相等性。

这是一个以同样方式工作的内置函数,著名的有问题的浮点 NaN:

>>> nan = float('nan')
>>> nan == nan
False
>>> nan is nan
True
>>> nan is float('nan')
False
>>> nan == float('nan')
False

这会导致集合的以下行为:

>>> nan = float('nan')
>>> myset = {nan}
>>> nan in myset
True
>>> float('nan') in myset
False

注意,当我们创建一个 new float 对象时它是假的。

【讨论】:

  • 您已经提前回答了我的另一个问题...文档中的该语句意味着线性搜索,这显然不是基于哈希的容器的情况...如果他们重写那行可能会更好在文档中。
  • @SorousHBakhtiary 嗯,不是真的。我认为很明显,他们并不是要暗示这一点,但无论如何,绝对不是这样
【解决方案2】:

So, why there is no __eq__ called for (John - 23)

这可以通过使用for in轻松实现

for i in s:
    print(i)
    if i == obj2:
        print('equal', i)
-------Membership testing-------

(John - 23)
__eq__ called for (John - 23) (John - 23)
(Soroush - 25)
__eq__ called for (Soroush - 25) (John - 23)

但显然,这不是你想要的,让我们添加另一个人并尝试in 运算符

obj1 = Person('Soroush', 25)
obj2 = Person('John', 23)
obj3 = Person('Kite', 23)
print('\n--------Creation of set---------\n')
s = {obj3, obj2, obj1}
print(s)
print('\n-------Membership testing-------\n')
print(obj2 in s)
-------Membership testing-------
__hash__ called for (John - 23)
__eq__ called for (Kite - 23) (John - 23)
True

现在通过改变设置元素的位置

.
.
s = {obj3, obj1, obj2}
.
.
__hash__ called for (John - 23)
__eq__ called for (Kite - 23) (John - 23)
__eq__ called for (Soroush - 25) (John - 23)
True

由此,我得出结论,True 来自对第三个元素的some parameter 的检查(最后一个示例)。最明显的是id 或 CPython 中的内存位置。因为对于这种情况

.
.
print(Person('John', 23) in s)
.
.
__hash__ called for (John - 23)
__eq__ called for (Kite - 23) (John - 23)
__eq__ called for (Soroush - 25) (John - 23)
__eq__ called for (John - 23) (John - 23)
False

你弄错了,因为id(Person('John', 23)) 显然与obj2 不同。类似的行为可以在for in 中重现,而不是== 使用is

obj1 = Person('Soroush', 25)
obj2 = Person('John', 23)
print('\n--------Creation of set---------\n')
s = {obj2, obj1}
print(s)
print('\n-------Membership testing-------\n')

for i in s:
    print(i)
    if i is obj2:
        print('equal', i)
     

print(obj2 in s)
-------Membership testing-------

(John - 23)
equal (John - 23)          ------------------------
(Soroush - 25)                                     |
                                                   |
                                                   |--you are missing this output in `in` check
__hash__ called for (John - 23)                    |
True      -----------------------------------------|

【讨论】:

    猜你喜欢
    • 2019-12-01
    • 1970-01-01
    • 2011-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-28
    • 2012-05-13
    • 1970-01-01
    相关资源
    最近更新 更多