起步
衆所周知,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() 去重原理:
- set() 函數中會先調用對象的
__hash__()
方法,獲取 hash 結果; - 如果 hash 結果相同,用比較操作符
==
(也就是調用函數__eq__()
)判斷二者的值是否相等; - 如果都相等,去重;否則,set() 認爲二者不同,兩個都保留到結果中。