成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

什么是可哈希對象,它的哈希值是怎么計算的?

開發(fā) 前端
像整數(shù)、浮點(diǎn)數(shù)、字符串等內(nèi)置的不可變對象都是可哈希的,可以作為字典的 key。而像列表、字典等可變對象則不是可哈希的,它們不可以作為字典的 key。

通過研究字典的底層實現(xiàn),我們找到了字典快速且高效的秘密,就是哈希表。而提到哈希表,必然繞不開哈希值,因為它決定了映射之后的索引。

如果想計算對象的哈希值,那么要保證對象必須是可哈希的。如果不可哈希,那么它就無法計算哈希值,自然也就無法作為字典的 key。那什么樣的對象是可哈希的呢?

  • 因為哈希值不能發(fā)生改變,所以對象必須是不可變對象;
  • 當(dāng)對象的哈希值相等時,要判斷對象是否相等,所以對象必須實現(xiàn) __eq__ 方法;

所以如果對象滿足不可變、并且實現(xiàn)了 __eq__ 方法,那么它就是可哈希的,只有這樣的對象才能作為字典的 key 或者集合的元素。

像整數(shù)、浮點(diǎn)數(shù)、字符串等內(nèi)置的不可變對象都是可哈希的,可以作為字典的 key。而像列表、字典等可變對象則不是可哈希的,它們不可以作為字典的 key。然后關(guān)于元組需要單獨(dú)說明,如果元組里面的元素都是可哈希的,那么該元組也是可哈希的,反之則不是。

# 鍵是可哈希的就行,值是否可哈希則沒有要求
d = {1: 1, "xxx": [1, 2, 3], 3.14: 333}

# 列表是可變對象,因此無法哈希
try:
    d = {[]: 123}
except TypeError as e:
    print(e)
    """
    unhashable type: 'list'
    """

# 元組也是可哈希的
d = {(1, 2, 3): 123}

# 但如果元組里面包含了不可哈希的對象
# 那么整體也會變成不可哈希對象
try:
    d = {(1, 2, 3, []): 123}
except TypeError as e:
    print(e)
    """
    unhashable type: 'list'
    """

而我們自定義類的實例對象也是可哈希的,并且哈希值是通過對象的地址計算得到的。

class Some:
    pass

s1 = Some()
s2 = Some()
print(hash(s1), hash(s2))
"""
8744065697364 8744065697355
"""

當(dāng)然 Python 也支持我們重寫哈希函數(shù),比如:

class Some:

    def __hash__(self):
        return 123

s1 = Some()
s2 = Some()
print(hash(s1), hash(s2))
"""
123 123
"""
print({s1: 1, s2: 2})
"""
{<__main__.Some object at 0x0000029C0ED045E0>: 1, 
 <__main__.Some object at 0x0000029C5E116F20>: 2}
"""

因為哈希值一樣,映射出來的索引自然也是相同的,所以在作為字典的 key 時,會發(fā)生沖突。由于類的實例對象之間默認(rèn)不相等,因此會改變規(guī)則重新映射,找一個可以寫入的位置。

如果兩個對象相等,它們的哈希值一定也相等。

注意:我們自定義類的實例對象默認(rèn)都是可哈希的,但如果類里面重寫了 __eq__ 方法,且沒有重寫 __hash__ 方法的話,那么這個類的實例對象就不可哈希了。

class Some:

    def __eq__(self, other):
        return True

try:
    hash(Some())
except TypeError as e:
    print(e)
    """
    unhashable type: 'Some'
    """

為什么會有這種現(xiàn)象呢?首先上面說了,在沒有重寫 __hash__ 方法的時候,哈希值默認(rèn)是根據(jù)對象的地址計算得到的。而且對象如果相等,那么哈希值一定是一樣的,并且不可變。

但我們重寫了 __eq__,相當(dāng)于控制了 == 操作符的比較結(jié)果,兩個對象是否相等就由我們來控制了,可哈希值卻還是根據(jù)地址計算得到的。因為兩個對象地址不同,所以哈希值不同,但是對象卻可以相等、又可以不相等,這就導(dǎo)致了矛盾。所以在重寫了__eq__、但是沒有重寫 __hash__ 的情況下,其實例對象便不可哈希了。

但如果重寫了 __hash__,那么哈希值就不再通過地址計算了,因此此時是可以哈希的。

class Some:

    def __eq__(self, other):
        return True

    def __hash__(self):
        return 123

s1 = Some()
s2 = Some()
print({s1: 1, s2: 2})
"""
{<__main__.Some object at 0x00000202D7D945E0>: 2}
"""

我們看到字典里面只有一個元素,因為重寫了 __hash__ 方法之后,計算得到的哈希值都是一樣的。如果沒有重寫 __eq__,實例對象之間默認(rèn)是不相等的,因此哈希值一樣,但是對象不相等,那么會重新映射。但我們重寫了 __eq__,返回的結(jié)果是 True,所以 Python 認(rèn)為對象是相等的,那么由于 key 的不重復(fù)性,只會保留一個鍵值對。

但需要注意的是,在比較相等時,會先比較地址是否一樣,如果地址一樣,那么哈希表會直接認(rèn)為相等。

class Some:

    def __eq__(self, other):
        return False

    def __hash__(self):
        return 123

    def __repr__(self):
        return "Some Instance"

s1 = Some()
# 我們看到 s1 == s1 為 False
print(s1 == s1)
"""
False
"""
# 但是只保留了一個 key,咦,兩個 key 不相等,難道不應(yīng)該重新映射嗎?
# 原因就是剛才說的,在比較是否相等之前,會先判斷地址是否一樣
# 如果地址一樣,那么認(rèn)為是同一個 key,直接判定相等
print({s1: 1, s1: 2})
"""
{Some Instance: 2}
"""

s2 = Some()
# 此時會保留兩個 key,因為 s1 和 s2 地址不同,s1 == s2 也為 False
# 所以哈希表認(rèn)為這是兩個不同的 key
# 但由于哈希值一樣,那么映射出來的索引也一樣
# 因此寫入 s2: 2 時相當(dāng)于發(fā)生了索引沖突,于是會重新映射
# 但總之這兩個 key 都會被保留
print({s1: 1, s2: 2})  
"""
{Some Instance: 1, Some Instance: 2}
"""

同樣的,我們再來看一個 Python 字典的例子。

d = {1: 123}

d[1.0] = 234
print(d)  # {1: 234}

d[True] = 345
print(d)  # {1: 345}

天哪嚕,這是咋回事?首先整數(shù)在計算哈希值的時候,得到的結(jié)果就是其本身;而浮點(diǎn)數(shù)顯然不是,但如果浮點(diǎn)數(shù)的小數(shù)點(diǎn)后面是 0,那么它和整數(shù)是等價的。

因此 3 和 3.0 的哈希值一樣,并且兩者也是相等的,因此它們被視為同一個 key,所以相當(dāng)于是更新。同理 True 也一樣,因為 bool 繼承自 int,所以它等價于 1,比如:9 + True = 10。因此 True 和 1 相等,并且哈希值也相等,那么索引 d[True] = 345 同樣相當(dāng)于更新。

但是問題來了,值更新了我們可以理解,字典里面只有一個元素也可以理解,可為什么 key 一直是 1 呢?理論上最終結(jié)果應(yīng)該是 True 才對啊。

其實這算是 Python 偷了個懶吧(開個玩笑),因為 key 的哈希值是一樣的,并且也相等,所以只會更新 value,而不會修改 key。

從字典在設(shè)置元素的時候我們也知道,如果將 key 映射成索引之后,發(fā)現(xiàn)哈希索引數(shù)組的槽沒有人用,那么就按照先來后到的順序?qū)㈡I值對存儲在鍵值對數(shù)組中,再把它在鍵值對數(shù)組中的索引存在哈希索引數(shù)組的指定槽中。

但如果發(fā)現(xiàn)槽有人用了,那么根據(jù)槽里面存的索引,去鍵值對數(shù)組中查找指定的 entry,然后比較兩個 key 是否相等。如果對應(yīng)的 key 不相等,則重新映射找一個新的槽;如果相等,則說明是同一個 key,那么把 value 換掉即可。

所以在替換元素的整個過程中,根本沒有涉及到對鍵的修改,因此在上面那個例子中,value 會變、但 key 始終是 1,而不是 True。

為了加深理解,我們再舉個例子:

d = {"高老師": 666}

class A:
    def __hash__(self):
        return hash("高老師")

    def __eq__(self, other):
        return True

# A() == "高老師" 為 True,兩者哈希值也一樣
# 所以相當(dāng)于對 key 進(jìn)行更新
d[A()] = 777
print(d)  # {'高老師': 777}

print(d["高老師"])  # 777
print(d[A()])  # 777

只要兩個對象相等,并且哈希值相等,那么對于哈希表來說,它們就是同一個 key。

另外我們反復(fù)在提哈希值,而哈希值是通過哈希函數(shù)運(yùn)算得到的,一個理想的哈希函數(shù)要保證哈希值盡量均勻地分布于整個哈希空間中,越是相近的值,其哈希值差別應(yīng)該越大。還是那句話,哈希函數(shù)對哈希表的好壞起著至關(guān)重要的作用。

以上我們就詳細(xì)地聊了聊對象的哈希值,如果對象可以計算哈希值,那么它一定實現(xiàn)了 __hash__ 方法,而內(nèi)置的不可變對象都實現(xiàn)了。

事實上內(nèi)置的哈希函數(shù) hash,本質(zhì)上也是調(diào)用了 __hash__。

print(hash("hello"))
print("hello".__hash__())
"""
-7465190714692855315
-7465190714692855315
"""

責(zé)任編輯:武曉燕 來源: 古明地覺的編程教室
相關(guān)推薦

2021-04-29 10:08:10

數(shù)據(jù)結(jié)構(gòu)哈希表

2021-09-03 07:23:59

哈希洪水攻擊黑客DDoS

2023-09-12 11:00:38

HashMap哈希沖突

2023-05-28 00:09:21

2020-07-20 08:30:37

算法哈希分布式系統(tǒng)

2021-07-27 08:57:10

算法一致性哈希哈希算法

2023-03-26 00:04:14

2024-11-25 12:20:00

Hystrix微服務(wù)架構(gòu)

2019-11-01 09:13:37

算法哈希緩存

2024-08-08 11:05:22

2022-02-24 23:37:19

區(qū)塊鏈錢包比特幣

2023-04-17 14:21:19

5G無線技術(shù)

2020-11-20 10:51:03

云計算

2009-08-20 16:45:03

C#哈希值

2021-10-28 22:31:11

存儲云存儲數(shù)據(jù)

2020-04-26 09:17:08

哈希傳遞身份驗證攻擊

2009-07-08 13:28:07

云計算Ubuntu服務(wù)器

2024-12-03 09:34:35

觀察者模 式編程Javav

2023-11-07 08:00:00

Kubernetes

2018-03-22 14:47:13

容器開發(fā)人員筆記本
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 欧美亚洲视频在线观看 | 中文字幕不卡视频在线观看 | 日本不卡一区 | 亚州精品天堂中文字幕 | 国产视频三级 | 亚洲激情在线观看 | 久久r精品| 亚洲 精品 综合 精品 自拍 | 久久久久国产一区二区三区四区 | 在线看亚洲 | 毛片一级网站 | 欧美精品成人一区二区三区四区 | 日韩成人精品一区二区三区 | 日韩在线视频播放 | 成人亚洲一区 | 久久精品国产99国产 | 日本淫视频 | 久久综合久久自在自线精品自 | 精品国产欧美一区二区三区成人 | av天天爽 | 谁有毛片 | 精品动漫一区 | 欧美在线一区二区三区 | 99视频在线免费观看 | 成人久久久 | 亚洲欧美日韩系列 | 天堂亚洲 | 亚洲一区二区三区在线视频 | 国产免费高清 | 精品欧美乱码久久久久久1区2区 | 九色网址 | 久久91精品国产一区二区 | 日本三级线观看 视频 | 欧美国产亚洲一区二区 | 日日骚网| 日韩一区二区在线播放 | 日韩一区二区免费视频 | 国产精品91网站 | 日本精品视频一区二区 | 欧美一区二 | 成人美女免费网站视频 |