上兩節講hash表,python裏的字典就是經過hash表去實現的。python
字典最經常使用的就是key, value存儲,常常用做緩存,他的key值惟一。緩存
內置庫裏collections.OrderDict保持了key的添加順序,用以前實現的hash表也能本身實現一個OrderDict。dom
根據hash表繼承他的類來實現字典的抽象數據類型,並支持items(),keys(),values(),賦值操做等。ide
代碼以下:函數
""" 繼承上一節的hash表的類 """ class DictADT(HashTable): #繼承HashTable類 def __setitem__(self, key, value): #賦值操做 self.add(key, value) #調用add方法,添加鍵值對 def __getitem__(self, key): #取值操做 if key is not self: #若是Key並無在當前的HashTable裏面,返回error異常 raise KeyError() else: return self.get(key) #不然,返回這個key對應的value def _iter_slot(self): #定義一個輔助方法 for slot in self._table: if slot not in (HashTable.EMPTY, HashTable.UNUSED): #對於非空的slot,若是slot不是空槽,也不是未被使用的槽 yield slot #遍歷槽 def items(self): #遍歷操做 for slot in self._iter_slot: yield (slot.key, slot,value) #按照python的定義,遍歷返回key,value的形式 def keys(self): #取key for slot in self._iter_slot(): yield slot.key def values(self): #取value for slot in self._iter_slot(): yield slot.value """ 單元測試 """ def test_dict_adt(): d = Dict() d['a'] = 1 #給'a'賦值,調用setitem方法 assert d['a'] == 1 #斷言值是1 d.remove('a') #刪除'a' import random l = list(range(30)) #獲取長度爲30的列表 random.shuffle(l) #把順序打亂 for i in l: d.add(i, i) #向字典中添加元素 for i in range(30): assert d.get(i) == i #斷言取得值等於i """測試迭代""" assert sorted(list(d.keys())) == sorted(l) #排序後的key的順序和排序後列表的順序一致
如此,字典就基本上實現出來了,主要的主體方法就是這些,都是比較經常使用的。
單元測試
Hashable(可哈希的)測試
做爲字典的key,必須是可哈希(hashable)的,也就是說不能使list等這種可變的對象。ui
舉例:.net
>>> d = dict() >>> d {} >>> d[[1]] = 1 #賦值會報錯 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list'
元組是不可變的對象
對象
>>> d[(1,2)] = 1 >>> d[(1,2)] 1
在python裏面有可哈希的概念,之因此說可變的對象不能做爲key,由於在他的生命週期裏面,他哈希的值會改變,這是就沒有辦法每次都哈希的到同一個位置,因此這種對象就不可哈希,也不會做爲key;而不可變對象,它的生命週期裏咱們用hash去獲取他的哈希值,他始終是保持一致,這種就能夠做爲python字典的key。
————————————————————
(1)可變對象與不可變對象的區別:
不可變對象:
該對象所指向的內存中的值不能被改變。當改變某個變量時候,因爲其所指的值不能被改變,至關於把原來的值複製一份後再改變,這會開闢一個新的地址,變量再指向這個新的地址。
可變對象:
該對象所指向的內存中的值能夠被改變。變量(準確的說是引用)改變後,其實是其所指的值直接發生改變,並無發生複製行爲,也沒有開闢新的出地址,通俗點說就是原地改變。
so, Python中,數值類型(int和float)、字符串str、元組tuple都是不可變類型。而列表list、字典dict、集合set是可變類型。
(2)瞭解__hash__和__eq__的魔術方法,以及什麼時候被調用?
① 方法:__hash__
意義:內建函數hash()調用的返回值,返回一個整數。若是定義這個方法該類的實例就可hash。
__hash__的使用場景有二:
a. 被內置函數hash()調用;
b. hash類型的集合對自身成員的hash操做:set(), frozenset([iterable]), dict(**kwarg)
如下是文檔說明:
object.__hash__(self) Called by built-in function hash() and for operations on members of hashed collections including set, frozenset, and dict.
若是自定義類沒定義__eq__()方法,那也不該該定義__hash__()方法。
若是定義了__eq__()方法沒有定義__hash__()方法,那麼它沒法做爲哈希集合的元素使用(這個hashable collections值得是set、frozenset和dict)。這實際上是由於重寫__eq__()方法後會默認把__hash__賦爲None(文檔後面有說),像list同樣。
若是定義可變對象的類實現了__eq__()方法,就不要再實現__hash__()方法,不然這個對象的hash值發生變化會致使被放在錯誤的哈希桶中。這個能夠用字典試一下,你的鍵值不在是一一對應的,只要能讓這兩個方法返回一致的對象都能改動那個本不屬於本身的值
參考資料:
http://www.javashuo.com/article/p-brwrxxix-nm.html(python中的__hash__和__eq__方法之間的一些使用問題)
https://blog.csdn.net/sinat_38068807/article/details/86519944(python 什麼時候單用__hash__或__eq__什麼時候一塊兒用)