Python的__hash__函数和__eq__函数

Python的__hash__函数和__eq__函数

可哈希的集合(hashed collections),需要集合的元素实现了__eq____hash__,而这两个方法可以作一个形象的比喻:
哈希集合就是很多个桶,但每个桶里面只能放一个球。
__hash__函数的作用就是找到桶的位置,到底是几号桶。
__eq__函数的作用就是当桶里面已经有一个球了,但又来了一个球,它声称它也应该装进这个桶里面(__hash__函数给它说了桶的位置),双方僵持不下,那就得用__eq__函数来判断这两个球是不是相等的(equal),如果是判断是相等的,那么后来那个球就不应该放进桶里,哈希集合维持现状。

class Foo:
    def __init__(self, item):
        self.item = item

    def __eq__(self, other):
        print('使用了equal函数的对象的id',id(self))
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False
    def __hash__(self):
        print('f'+str(self.item)+'使用了hash函数')
        return hash(self.item)       
f1 = Foo(1)
f2 = Foo(2)
f3 = Foo(3)
fset = set([f1, f2, f3])
print(fset)
print()
f = Foo(3)
fset.add(f)
print('f3的id:',id(f3))
print('f的id:',id(f))

运行结果:

f1使用了hash函数
f2使用了hash函数
f3使用了hash函数
{<__main__.Foo object at 0x0000023769AB67C0>, <__main__.Foo object at 0x0000023769AC5C10>, <__main__.Foo object at 0x0000023769AC5C40>}

f3使用了hash函数
使用了equal函数的对象的id 2437019360320
f3的id: 2437019360320
f的id: 2437019360368

可见,在将f1,f2,f3加入到set中时,每次都会调用一次__hash__函数。
由于我定义的___hash__函数是return hash(self.item),所以f和f3找到的桶的位置是同一个位置,因为它俩的item是相同的。当执行fset.add(f)时,f就会调用它自身的__hash__函数,以找到f所属于的桶的位置。但此时桶里已经有别的球了,所以这时候就得用上__eq__来判断两个对象是否相等,从输出可以看出,是已有对象调用__eq__来和后来的对象进行比较(看对象的id)。
这里如果是删除操作fset.remove(Foo(3)),道理也是一样,先用hash找到桶的位置,如果桶里有球,就判断这两个球是否相等,如果相等就把桶里那个球给扔掉。

官方解释

当可哈希集合(set,frozenset,dict)调用hash函数时,应该返回一个int值。唯一的要求就是,如果判断两个对象相等,那么他们的hash值也应该相等。当比较两个对象相等时是使用对象的成员来比较时,建议要把成员弄进元祖里,再得到这个元祖的hash值来比较。

当class没有定义__eq__()方法时,那么它也不应该定义__hash__()方法。如果它定义了__eq__()方法,却没有定义__hash__()方法,那么这个类的实例就不能在可哈希集合使用。如果一个类定义了一个可变对象(这里应该是指class的成员之一为可变对象),且implement了__eq__()方法,那么这个类就不应该implement hash()方法,因为可哈希对象的实现(implement )要求键值key的hash值是不变的(如果一个对象的hash值改变了,那么它会被放在错误的hash桶里)

用户定义的类中都有默认的__eq__和__hash__方法;有了它,所有的对象实例都是不等的(除非是自己和自己比较),在做x == y比较时是和这个等价的hash(x) == hash(y)。

只实现__eq__(错误示范)

class Foo:
    def __init__(self, item):
        self.item = item

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

f1 = Foo(1)
f2 = Foo(1)
f3 = Foo(1)
print(set([f1, f2, f3]))

运行报错:

Traceback (most recent call last):
  File "c:/Users/Administrator/Desktop/MyFile/MyCoding/Other/hashtest.py", line 14, in <module>
    print(set([f1, f2, f3]))
TypeError: unhashable type: 'Foo'
posted @ 2019-11-11 21:24  KadyCui  阅读(4588)  评论(0编辑  收藏  举报