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'


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM