簡單談談Python中的dict{}與set()與__eq__、__hash__的關係

1:預備知識(Hash)
1.1:在Python3中,dict和set數據結構要求鍵值key是可hash的,這樣可以保證鍵值key的唯一性。簡要的說可hash的數據類型就是不可變的數據結構(字符串str、元組tuple、對象集objects),它是一個將大體量數據轉化爲很小數據的過程,甚至可以僅僅是一個數字,以便我們可以用在固定的時間複雜度下查詢它,所以,hash對高效的算法和數據結構很重要。這點和數字圖像處理中經常用到的LUT(look-up-table)很像,感興趣的可以百度一下。
1.2:不可哈希的類型有列表(list可以append和pop進行變化)、字典(dict可以添加key與value變化)、集合(set()具有add等方法)
這裏簡單說一下,使用Hash算法的數據結構叫做哈希表,也叫散列表,主要是爲了提高查詢的效率。他通過映射來訪問自己想要的值,而Python3中的字典就是一個典型的哈希表。
1.3:Python3中使用 __hash__ 返回一個int值,用來唯一標記這個對象。

class test:
    def __init__(self,value):
        self.val = value
p1 = test(1)
p2 = test(2)
p3 = test(3)
print(set([p1,p2,p3]))

In [49]:runfile('C:/Users/16288/Desktop/ALBB.py', wdir='C:/Users/16288/Desktop')
{<__main__.test object at 0x0000022563BB04A8>, <__main__.test object at 0x0000022563BB01D0>, <__main__.test object at 0x0000022563BB0160>}

可以看到test類自動繼承了__hash__ 函數,輸出了值。
1.4:什麼是__eq__與__hash__?
在官方文檔中,它們倆定義如下:

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

首先考慮非類對象:

a = 2
b = 3
c = 4
d = 2
print(set([a,b,c,d]))

其結果爲:
{2, 3, 4}
查看他們的hash,可以看到,其中a和d因爲值一樣,所以hash後的值也是一樣的。
在這裏插入圖片描述
那麼對於類對象呢?
做一個比喻,譬如你有1~N號盆,每個盆只能放一類球,又有m個類型的小球要放到盆裏面。而__hash__作用就是判斷第i個小球可以放到哪個盆裏面,找到盆的文章,__eq__的作用就是在已經找到了盆的情況下,若這個盆已經有了一個小球,但又來了一個球,它聲稱它也應該裝進這個盆裏面(__hash__函數給它說了盆的位置),雙方僵持不下,那就得用__eq__函數來判斷這兩個球是不是相等的(equal),如果是判斷是相等的,那麼後來那個球就不應該放進盆裏,哈希集合維持現狀。

class Foo:
    def __init__(self,name,count):
        self.name = name
        self.count = count
    def __hash__(self):
        print("%s調用了哈希方法"%self.name)
        return hash(self.count)
    def __eq__(self, other):
        print("%s調用了eq方法"%self.name)
        return self.__dict__==other.__dict__
f1 = Foo('f1',1)
f2 = Foo('f2',2)
f3 = Foo('f3',3)
ls = set([f1,f2,f3])
print(ls)
print('===================')
f4 = Foo('f4',3)
ls.add(f4)
print('f3的id:',id(f3))
print('f4的id:',id(f4))

結果如下:
在這裏插入圖片描述
其中count是key值,也就是需要hash的值,而name和count會在後面eq方法中的__dict__出現,可以看到,f3和f4的hash值是相等的,但是set並沒有這麼簡單就判斷f4,f3是重複的,而是繼續調用了eq方法判斷f3和f4是否相等,只有相等的時候纔會認爲是同一個。
爲了驗證上面的假設,我們可以執行以下輸入:

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

結果爲:
在這裏插入圖片描述
很明顯,這裏只存了兩個值,所以不難看出,f1和f2被set判斷重複,只取了一個。
2:結論
set的去重是通過兩個函數__hash__和__eq__結合實現的。
1、當兩個變量的哈希值不相同時,就認爲這兩個變量是不同的
2、當兩個變量哈希值一樣時,調用__eq__方法,當返回值爲True時認爲這兩個變量是同一個,應該去除一個。返回FALSE時,不去重
而dict直接採用的是hashmap的結構。
PS:實際上set可以看作是沒有value的dict,請讀者自行思考原因。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章