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'