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'