Python中字典的鍵為什么要是不可變類型!稚疹!

很多python初學(xué)者經(jīng)常會(huì)有這樣的疑問(wèn)居灯,為什么Python有tuple(元組)和list(列表)兩種類型?為什么tuple可以作為字典的key内狗,list不可以怪嫌?要理解這個(gè)問(wèn)題,首先要明白python的字典工作原理其屏。

Python的字典是如何工作的

在Python中喇勋,字典也就是一個(gè)個(gè)的“映射”,將key映射到value:

# 對(duì)一個(gè)特定的key可以得到一個(gè)value value = d[key]

為了實(shí)現(xiàn)這個(gè)功能偎行,Python必須能夠做到川背,給出一個(gè)key,找到哪一個(gè)value與這個(gè)key對(duì)應(yīng)蛤袒。先來(lái)考慮一種比較簡(jiǎn)單的實(shí)現(xiàn)熄云,將所有的key-value鍵值對(duì)存放到一個(gè)list中,每當(dāng)需要的時(shí)候妙真,就去遍歷這個(gè)list缴允,用key去和鍵值對(duì)的key匹配,如果相等珍德,就拿到value练般。但是這種實(shí)現(xiàn)在數(shù)據(jù)量很大的時(shí)候就變得很低效。它的算法復(fù)雜度是O(n)锈候,n是存放鍵值對(duì)的數(shù)量薄料。

為此,Python使用了hash(哈希)的方法來(lái)實(shí)現(xiàn)泵琳,要求每一個(gè)存放到字典中的對(duì)象都要實(shí)現(xiàn)hash函數(shù)摄职,這個(gè)函數(shù)可以產(chǎn)生一個(gè)int值誊役,叫做hash value(哈希值),通過(guò)這個(gè)int值谷市,就可以快速確定對(duì)象在字典中的位置蛔垢。

這個(gè)查詢的大致過(guò)程如下:

def lookup(d, key): '''字典的查詢過(guò)程概括為下面3步: 1. 通過(guò)hash函數(shù)將key計(jì)算為哈希值. 2. 通過(guò)hash值確定一個(gè)位置,這個(gè)位置是一個(gè)存放著 可能存在沖突的元素的數(shù)組(很多地方叫做“桶”迫悠,bucket)鹏漆, 每一個(gè)元素都是一個(gè)鍵值對(duì),理想情況下及皂,這個(gè)數(shù)組里只有1個(gè)元素. 3. 遍歷這個(gè)數(shù)組甫男,找到目標(biāo)key,返回對(duì)應(yīng)的value. ''' h = hash(key)# step 1 cl = d.data[h]# step 2 for pairin cl:# step 3 if key == pair[0]: return pair[1] else: raise KeyError, "Key %s not found." % key

要使這個(gè)查找過(guò)程正常工作验烧,hash函數(shù)必須滿足條件: 如果兩個(gè)key產(chǎn)生了不同的hash value,那么這兩個(gè)key對(duì)象是不想等的又跛。 即

for alli1, i2, if hash(i1) != hash(i2), then i1 != i2

否則的話碍拆,hash value不同,對(duì)象卻相同慨蓝,那么相同的對(duì)象產(chǎn)生不同的hash value感混,查找的時(shí)候就會(huì)進(jìn)錯(cuò)桶(step 2),在錯(cuò)誤的桶里永遠(yuǎn)也找不到你要找的value礼烈。

另外弧满,要讓字典保持高查找效率,還要保證: 當(dāng)兩個(gè)key產(chǎn)生相同的hash value此熬,那么他們是相等的庭呜。

for alli1, i2, if hash(i1) == hash(i2), then i1 == i2

這樣做的目的是,盡量滿足每個(gè)hash桶只有一個(gè)元素犀忱。為什么要這樣呢募谎? 考慮下面這個(gè)hash函數(shù)。

def hash(obj): return 1

這個(gè)hash函數(shù)是滿足上面我們談的第一個(gè)條件的:如果兩個(gè)key的hash value不同阴汇,那么兩個(gè)key對(duì)象不相同数冬。因?yàn)樗械膶?duì)象產(chǎn)生的hash value都是1,所以不存在能產(chǎn)生不同hash value的key搀庶,也就不存在不滿足的情況拐纱。但是這樣做的壞處是,因?yàn)樗械膆ash value都相同哥倔,所以就把所有的對(duì)象分到了同一個(gè)地方秸架。查找的時(shí)候,進(jìn)行到第三步未斑,遍歷的效率就變成了O(n).

Hash函數(shù)應(yīng)該保證所有的元素平均的分配到每一個(gè)桶中咕宿,理想的情況是币绩,每一個(gè)位置只有一個(gè)元素。

字典Key要滿足的要求

經(jīng)過(guò)上面的討論府阀,我們應(yīng)該明白Python為什么對(duì)字典的key有這樣的要求了:

要作為字典的key缆镣,對(duì)象必須要支持hash函數(shù)(即__hash__),相等比較(__eq__或__cmp__)试浙,并且滿足上面我們討論過(guò)的條件董瞻。

List為什么不能作為key

至于這個(gè)問(wèn)題,最直接的答案就是:list沒(méi)有支持__hash__方法田巴,那么為什么呢钠糊?

對(duì)于list的hash函數(shù),我們可能有下面兩種實(shí)現(xiàn)的方式:

第一種壹哺,基于id抄伍。這滿足條件,“如果hash值不同管宵,那么他們的id當(dāng)然不同”截珍。但考慮到list一般是作為容器,基于id來(lái)hash可能會(huì)導(dǎo)致下面兩種情況:

用相同的list作為key去字典中找某個(gè)元素可能會(huì)得到不同的結(jié)果箩朴,因?yàn)槭腔趇d hash的岗喉,所以即使他們的內(nèi)容相同,字典依然將他們作為不同的元素對(duì)待炸庞。 創(chuàng)建一個(gè)一模一樣的list用字典查找永遠(yuǎn)會(huì)得到一個(gè)KeyError钱床。

第二種,基于內(nèi)容埠居。tuple就是這樣做的查牌,但是要注意一點(diǎn),list是可以修改的拐格。當(dāng)list修改之后僧免,你就永遠(yuǎn)別想再?gòu)淖值渲心没貋?lái)了。見下面的代碼捏浊。

>>> l = [1, 2] >>> d = {} >>> d[l] = 42 >>> l.append(3) >>> d[l] # 原來(lái)的hash值是基于[1, 2]hash的懂衩, # 現(xiàn)在是基于[1, 2, 3],所以找不到 Traceback (mostrecentcalllast): File "", line 1, in ? KeyError: [1, 2, 3] >>> d[[1, 2]] # 基于hash [1, 2] # 但是遍歷的時(shí)候找不到key相等的鍵值對(duì) #(因?yàn)樽值淅锏膋ey變成了[1, 2, 3] Traceback (mostrecentcalllast): File "", line 1, in ? KeyError: [1, 2]

鑒于兩種實(shí)現(xiàn)的方式都存在一定的副作用金踪,所以Python規(guī)定:

內(nèi)置的list不能作為字典的key.

但tuple是不可變浊洞,所以tuple可以作為字典的key。

自定義的類型作為字典的Key

用戶自定義的類型就可以作為key了胡岔,默認(rèn)的 hash(object) 是 id(object) , 默認(rèn)的 cmp(object1,object2) 是 cmp(id(object1),id(object2))法希, 同樣是可以修改的對(duì)象,為什么這里就沒(méi)有上面說(shuō)的問(wèn)題呢靶瘸?

一般來(lái)說(shuō)苫亦,在映射中比較常見的需求是用一個(gè)object替換掉原來(lái)的毛肋,所以id比內(nèi)容更重要,就可以基于id來(lái)hash 如果內(nèi)容重要的話屋剑,自定義的類型可以通過(guò)覆蓋__hash__函數(shù)和__cmp__函數(shù)或__eq__函數(shù)來(lái)實(shí)現(xiàn)

值得注意的是:將對(duì)象和一個(gè)value關(guān)聯(lián)起來(lái)润匙,更好的做法是將value設(shè)置為對(duì)象的一個(gè)屬性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唉匾,一起剝皮案震驚了整個(gè)濱河市孕讳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巍膘,老刑警劉巖厂财,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異峡懈,居然都是意外死亡璃饱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門逮诲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)帜平,“玉大人,你說(shuō)我怎么就攤上這事梅鹦。” “怎么了冗锁?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵齐唆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我冻河,道長(zhǎng)箍邮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任叨叙,我火速辦了婚禮锭弊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘擂错。我一直安慰自己味滞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布钮呀。 她就那樣靜靜地躺著剑鞍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪爽醋。 梳的紋絲不亂的頭發(fā)上蚁署,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音蚂四,去河邊找鬼光戈。 笑死哪痰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的久妆。 我是一名探鬼主播晌杰,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼镇饺!你這毒婦竟也來(lái)了乎莉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奸笤,失蹤者是張志新(化名)和其女友劉穎惋啃,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體监右,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡边灭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了健盒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绒瘦。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扣癣,靈堂內(nèi)的尸體忽然破棺而出惰帽,到底是詐尸還是另有隱情,我是刑警寧澤父虑,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布该酗,位于F島的核電站,受9級(jí)特大地震影響士嚎,放射性物質(zhì)發(fā)生泄漏呜魄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一莱衩、第九天 我趴在偏房一處隱蔽的房頂上張望爵嗅。 院中可真熱鬧,春花似錦笨蚁、人聲如沸睹晒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)册招。三九已至,卻和暖如春勒极,著一層夾襖步出監(jiān)牢的瞬間是掰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工辱匿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留键痛,地道東北人炫彩。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像絮短,于是被迫代替她去往敵國(guó)和親江兢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉丁频,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,732評(píng)論 0 9
  • java筆記第一天 == 和 equals ==比較的比較的是兩個(gè)變量的值是否相等杉允,對(duì)于引用型變量表示的是兩個(gè)變量...
    jmychou閱讀 1,504評(píng)論 0 3
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,449評(píng)論 0 17
  • //Clojure入門教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語(yǔ)閱讀 3,688評(píng)論 0 7
  • 外面下起了雪席里,落地即化叔磷,施工中的路面上錯(cuò)落地鋪滿了水泥、河砂奖磁。天昏暗改基,地泥濘,早晨剛擦得白得發(fā)亮的平底鞋很快變得狼...
    禪詩(shī)閱讀 429評(píng)論 0 1