set() 去重原理



起步

衆所周知,set() 是 Python 中的"天然去重因子"。對一串數據如:lyst = [1, 1, 2, 4, 4],我們常常 set 一下,也就是:set(lyst),達到去重目的。

那麼,set() 是如何去重的呢?

自定義的數據結構

爲了貼合實際的開發需求,我們常需要自定義數據結構。拿通用示例 Student 來說。

class Student(object):
    def __init__(self, name, age, sid):
        self.name = name
        self.age = age
        self.sid = sid

現在,我們實例兩個 Student 對象,分別是 stu1 和 stu2,其名字 name,年齡 age,學號 sid 相同。現實生活中,可以認爲這兩個學生是同一人。

stu1 = Student("zhong", 15, 11198)
stu2 = Student("zhong", 15, 11198)

print(set([stu1, stu2]))
# 輸出:{<__main__.Student object at 0x0030FE10>, <__main__.Student object at 0x0030FAD0>}

然而 set() 並不這樣認爲,因此沒有實現去重效果。

__eq__函數

事實上,我們用比較操作符 == 會發現,Python 解釋器認爲 stu1 並不等於 stu2。

print(stu1 == stu2)  # 輸出:False

會有上述現象,是因爲程序沒有按照現實需求運行。現實需求是:如果名字、年齡、學號都相同,那一定就是同一個人,因而我們需要重寫魔法方法 __eq__()。代碼如下所示:

class Student(object):
    def __init__(self, name, age, sid):
        self.name = name
        self.age = age
        self.sid = sid

    def __eq__(self, other):
        return self.name == other.name and \
               self.age == other.age and \
               self.sid == other.sid

stu1 = Student("zhong", 15, 11198)
stu2 = Student("zhong", 15, 11198)
print(stu1 == stu2)  # 輸出:True

現在我們是不是可以用 set 去重了呢?

print(set([stu1, stu2]))
---------------------------------
Traceback (most recent call last):
  File "xxxxxxxxx", line 18, in <module>
    print(set([stu1, stu2]))
TypeError: unhashable type: 'Student'

很遺憾,解釋器報錯了。它說 Student 類型的對象不能哈希。

__hash__函數

當我們沒有爲 Student 添加 __eq__() 函數時,set() 還不會報錯,現在卻說不能哈希?好在,我們可以重寫 __hash__() 方法,改變原來的默認的哈希處理邏輯。

class Student(object):
    def __init__(self, name, age, sid):
        self.name = name
        self.age = age
        self.sid = sid

    def __eq__(self, other):
        return self.name == other.name and \
               self.age == other.age and \
               self.sid == other.sid

    def __hash__(self):
        return hash((self.name, self.age, self.sid))

stu1 = Student("zhong", 15, 11198)
stu2 = Student("zhong", 15, 11198)
print(set([stu1, stu2]))
# 輸出:{<__main__.Student object at 0x0030FE10>}

爲方便起見,這裏藉助了 tuple 的不可變特性,使其能夠正確通過哈希處理。此時我們再用 set() 去重,發現成功了!

倘若在上述代碼的基礎上,試圖把 eq 函數去掉,你會發現 set() 去重失效了。儘管它們的哈希結果相同。

class Student(object):
    def __init__(self, name, age, sid):
        self.name = name
        self.age = age
        self.sid = sid

    def __hash__(self):
        return hash((self.name, self.age, self.sid))

stu1 = Student("zhong", 15, 11198)
stu2 = Student("zhong", 15, 11198)
print(set([stu1, stu2]))  # 輸出:{<__main__.Student object at 0x00A9FAD0>, <__main__.Student object at 0x00A9FE10>}
print(hash(stu1) == hash(stu2))  # 輸出: True

去重原理

經過前面一步步推導,我們也得出了 set() 去重原理:

  1. set() 函數中會先調用對象的 __hash__() 方法,獲取 hash 結果;
  2. 如果 hash 結果相同,用比較操作符 == (也就是調用函數 __eq__())判斷二者的值是否相等;
  3. 如果都相等,去重;否則,set() 認爲二者不同,兩個都保留到結果中。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章