Python - class __hash__ 方法和集合

24

我正在使用 Python 类的 set()__hash__ 方法来防止向集合中添加相同的哈希对象。根据 Python 数据模型文档set() 将相同哈希对象视为同一对象,并仅添加它们一次。

但下面的行为与此不同:

class MyClass(object):

    def __hash__(self):
        return 0

result = set()
result.add(MyClass())
result.add(MyClass())

print(len(result)) # len = 2

当涉及到字符串值时,它可以正确地工作。

result.add('aida')
result.add('aida')

print(len(result)) # len = 1

我的问题是:为什么在集合中相同的哈希对象不一样?

2个回答

37

你的阅读有误。__eq__方法用于进行相等性检查。文档只是说明对于两个对象ab,若a == b(即a.__eq__(b))为真,则它们的__hash__值也必须相同。

这是一个常见的逻辑错误:a == b为真意味着暗示hash(a) == hash(b)也为真。然而,蕴含并不一定意味着等价关系,即除了前述内容外,hash(a) == hash(b)也意味着a == b

为使所有的MyClass实例彼此相等,你需要为它们提供一个__eq__方法;否则Python将比较它们的身份标识符。这可能会起到作用:

class MyClass(object):
    def __hash__(self):
        return 0
    def __eq__(self, other):
        # another object is equal to self, iff 
        # it is an instance of MyClass
        return isinstance(other, MyClass)

现在:
>>> result = set()
>>> result.add(MyClass())
>>> result.add(MyClass())
1

实际上,您应该基于对象的那些用于__eq__比较的属性来确定__hash__,例如:

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

    def __eq__(self, other):
        return isinstance(other, Person) and self.ssn == other.ssn

    def __hash__(self):
        # use the hashcode of self.ssn since that is used
        # for equality checks as well
        return hash(self.ssn)

p = Person('Foo Bar', 123456789)
q = Person('Fake Name', 123456789)
print(len({p, q})  # 1

1
有没有办法选择保留在集合中的对象?比如更喜欢'Foo Bar'而不是'Fake Name' - Allie Fitter
1
@alliefitter 不是用哈希和等号。 - Antti Haapala -- Слава Україні

14

Sets需要两个方法使对象可哈希: __hash____eq__。当两个实例被认为相等时,它们必须返回相同的哈希值。如果哈希在集合中存在,并且该实例与具有相同哈希值的实例之一被认为是相等的,则认为该实例已经存在于集合中。

您的类没有实现__eq__,因此默认的object.__eq__将被使用,它仅在obj1 is obj2也为真时返回true。换句话说,只有当它们是完全相同的实例时,两个实例才被视为相等。

仅仅因为它们的哈希值匹配并不意味着对于集合来说它们是唯一的; 即使具有不同哈希值的对象也可能最终位于相同的哈希表槽中,因为使用哈希除以表大小的余数。

添加您自己的自定义__eq__方法,当两个实例应该相等时返回True

def __eq__(self, other):
    if not isinstance(other, type(self)):
        return False
    # all instances of this class are considered equal to one another
    return True

好奇为什么需要这两种方法,是否有性能上的好处,在执行eq之前先检查哈希值? - Davos
1
@Davos 集合被实现为 哈希表。是的,使用哈希表意味着集合成员测试需要 O(1) 的常数时间,而不是列表的 O(N),因此这是一种性能选择。 - Martijn Pieters

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