字典(dict)是存儲key/value數據的容器,也就是所謂的map、hash、關聯數組。不管是什麼稱呼,都是鍵值對存儲的方式。html
在python中,dict類型使用大括號包圍:python
D = {"key1": "value1", "key2": "value2", "key3": "value3"}
dict對象中存儲的元素沒有位置順序,因此dict不是序列,不能經過索引的方式取元素。dict是按照key進行存儲的,因此須要經過key做爲定位元素的依據,好比取元素或修改key對應的value。好比:數據庫
D['key1'] # 獲得value1 D['key2'] # 獲得value2 D['key3'] # 獲得value3
dict是一個hashtable數據結構,除了數據類型的聲明頭部分,還主要存儲了3部分數據:一個hash值,兩個指針。下面詳細解釋dict的結構。數組
下面是一個Dict對象:數據結構
D = {"key1": "value1", "key2": "value2", "key3": "value3"}
它的結構圖以下:app
這個圖很容易理解,key和value一一對應,只不過這裏多加了一個hash值而已。但這只是便於理解的結構,它並不是正確。看源碼中對dict類型的簡單定義。函數
typedef struct { /* Cached hash code of me_key. */ Py_hash_t me_hash; PyObject *me_key; PyObject *me_value; } PyDictKeyEntry;
從源碼中可知,一個hash值,這個hash值是根據key運用內置函數hash()來計算的,佔用8字節(64位機器)。除了hash值,後面兩個是指針,這兩個指針分別是指向key、指向value的指針,每一個指針佔用一個機器字長,也便是說對於64位機器各佔用8字節,因此一個dict的元素,除了實際的數據佔用的內存空間,還額外佔用24字節的空間。測試
因此,正確的結構圖以下:指針
對於存儲dict元素的時候,首先根據key計算出hash值,而後將hash值存儲到dict對象中,與每一個hash值同時存儲的還有兩個引用,分別是指向key的引用和指向value的引用。code
若是要從dict中取出key對應的那個記錄,則首先計算這個key的hash值,而後從dict對象中查找這個hash值,能找到說明有對應的記錄,因而經過對應的引用能夠找到key/value數據。
dict是可變的,能夠刪除元素、增長元素、修改元素的value。這些操做的過程與上面的過程相似,都是先hash,並根據hash值來存儲或檢索元素。
這裏須要注意的是,在python中,能hashable的數據類型都必須是不可變類型的,因此列表、集合、字典不能做爲dict的key,字符串、數值、元組均可以做爲dict的key(類的對象實例也能夠,由於自定義類的對象默認是不可變的)。
# 字符串做爲key >>> D = {"aa":"aa","bb":"bb"} >>> D {'aa': 'aa', 'bb': 'bb'} # 數值做爲key >>> D = {1:"aa","bb":"bb"} >>> D[1] 'aa' # 元組做爲key >>> D = {(1,2):"aa","bb":"bb"} >>> D {(1, 2): 'aa', 'bb': 'bb'} # 列表做爲key,報錯 >>> D = {[1,2]:"aa","bb":"bb"} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list'
由於元素存儲到dict的時候,都通過hash()計算,且存儲的其實是key對應的hash值,因此dict中各個元素是無序的,或者說沒法保證順序。因此,遍歷dict獲得的元素結果也是無序的。
# python 3.5.2 >>> d = {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> d {'four': 4, 'two': 2, 'three': 3, 'one': 1}
無序是理論上的。可是在python 3.7中,已經保證了python dict中元素的順序和插入順序是一致的。
Changed in version 3.7: Dictionary order is guaranteed to be insertion order. This behavior was an implementation detail of CPython from 3.6.
# python 3.7.1 >>> d = {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> d {'one': 1, 'two': 2, 'three': 3, 'four': 4}
雖保證了順序,但後面介紹dict的時候,仍然將它看成無序來解釋。
python中list是元素有序存儲的序列表明,dict是元素無序存儲的表明。它們均可變,是python中最靈活的兩種數據類型。
可是:
換句話說,dict是空間換時間,list是時間換空間。
其實從dict和list的數據結構上很容易能夠看出dict要比list佔用的內存大。不考慮存儲元素的實際數據空間,list存儲每一個元素只需一個指針共8字節(64位機器)便可保存,而dict至少須要24字節(64位機器),不只如此,hash表結構中每一個hash桶基本上會空餘1/3以上的空間。
有幾種構造字典的方式:
("one",1)
、["two",2]
>>> D = {} # 空字典 >>> type(D) <class 'dict'> >>> D = {"key1": "value1", "key2": "value2", "key3": "value3"} >>> D {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} >>> a = dict(one=1, two=2, three=3) >>> b = {'one': 1, 'two': 2, 'three': 3} >>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) >>> d = dict([('two', 2), ('one', 1), ('three', 3)]) >>> e = dict({'three': 3, 'one': 1, 'two': 2}) >>> f = dict([('two', 2), ('one', 1), ('three', 3)], four=4, five=5)
fromkey(seq,value)
是dict的類方法,因此可直接經過dict類名來調用(固然,使用已存在的對象來調用也沒有問題)。它構造的字典的key來自於給定的序列,值來自於指定的第二個參數,若是沒有第二個參數,則全部key的值默認爲None。因此,第二個參數是構造新dict時的默認值。
例如,構造一個5元素,key全爲數值的字典:
>>> dict.fromkeys(range(5)) {0: None, 1: None, 2: None, 3: None, 4: None} >>> dict.fromkeys(range(5), "aa") {0: 'aa', 1: 'aa', 2: 'aa', 3: 'aa', 4: 'aa'}
再例如,根據已有的dict來初始化一個新的dict:
>>> d = dict(one=1, two=2, three=3, four=4, five=5) >>> dict.fromkeys(d) {'one': None, 'two': None, 'three': None, 'four': None, 'five': None} >>> dict.fromkeys(d, "aa") {'one': 'aa', 'two': 'aa', 'three': 'aa', 'four': 'aa', 'five': 'aa'}
由於key的來源能夠是任意序列,因此也能夠從元組、列表、字符串中獲取。
>>> dict.fromkeys("abcd","aa") {'a': 'aa', 'b': 'aa', 'c': 'aa', 'd': 'aa'} >>> L = ["a", "b", "c", "d"] >>> dict.fromkeys(L) {'a': None, 'b': None, 'c': None, 'd': None} >>> T = ("a", "b", "c", "d") >>> dict.fromkeys(L) {'a': None, 'b': None, 'c': None, 'd': None}
dict的copy()方法會根據已有字典徹底拷貝成一個新的字典副本。但須要注意的是,拷貝過程是淺拷貝。
>>> d = {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> dd = d.copy() >>> dd {'three': 3, 'one': 1, 'two': 2, 'four': 4} >>> id(d["one"]), id(dd["one"]) (10919424, 10919424)
官方手冊:https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
經過key便可檢索到元素。
>>> d {'one': 1, 'two': 2, 'three': 3} >>> d["one"] 1 >>> d["four"] = 4 >>> d {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> d["ten"] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'ten'
對於dict類型,檢索不存在的key時會報錯。但若是本身去定義dict的子類,那麼能夠本身重寫__missing__()
方法來決定檢索的key不存在時的行爲。例如,對於不存在的鍵老是返回None。
>>> d = {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> class mydict(dict): ... def __missing__(self, key): ... return None ... >>> dd = mydict(d) >>> dd {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> dd["ten"] >>> print(dd["ten"]) None
get(key,default)方法檢索dict中的元素,若是元素存在,則返回對應的value,不然返回指定的default值,若是沒有指定default,且檢索的key又不存在,則返回None。這正好是上面自定義dict子類的功能。
>>> d = {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> d.get("two") 2 >>> d.get("six","not exists") 'not exists' >>> print(d.get("six")) None
len()函數能夠用來查看字典有多少個元素:
>>> d {'three': 3, 'four': 4, 'two': 2, 'one': 1} >>> len(d) 4
setdefault(key,default)方法檢索並設置一個key/value,若是key已存在,則直接返回對應的value,若是key不存在,則新插入這個key並指定其value爲default並返回這個default,若是沒有指定default,key又不存在,則默認爲None。
>>> d.setdefault("one") 1 >>> d.setdefault("five") {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': None} >>> d.setdefault("six",6) 6 >>> d {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': None, 'six': 6}
update(key/value)方法根據給定的key/value對更新已有的鍵,若是鍵不存在則新插入。key/value的表達方式有多種,只要能表達出key/value的配對行爲就能夠。好比已有的dict做爲參數,key=value的方式,2元素的迭代容器對象。
>>> d {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> d.update(five=5, six=6) # key=value的方式 >>> d {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6} >>> d.update({"one":11, "two":22}) # dict做爲參數 >>> d {'one': 11, 'two': 22, 'three': 3, 'four': 4, 'five': 5, 'six': 6} >>> d.update([("five",55),("six",66)]) # 列表中2元素的元組 >>> d {'one': 11, 'two': 22, 'three': 3, 'four': 4, 'five': 55, 'six': 66} >>> d.update((("five",55),("six",66))) # 這些均可以 >>> d.update((["five",55],["six",66])) >>> d.update(zip(["five","six"],[55,66]))
del D[KEY]能夠用來根據key刪除字典D中給定的元素,若是元素不存在則報錯。
>>> d = {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> del d["four"] >>> d {'three': 3, 'two': 2, 'one': 1} >>> del d["five"] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'five'
clear()方法用來刪除字典中全部元素。
>>> d = {'three': 3, 'four': 4, 'two': 2, 'one': 1} >>> d.clear() >>> d {}
pop(key,default)用來移除給定的元素並返回移除的元素。但若是元素不存在,則返回default,若是不存在且沒有給定default,則報錯。
>>> d = {'three': 3, 'four': 4, 'two': 2, 'one': 1} >>> d.pop("one") 1 >>> d.pop("five","hello world") 'hello world' >>> d.pop("five") Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'five'
popitem()用於移除並返回一個(key,value)
元組對,每調用一次移除一個元素,沒元素可移除後將報錯。在python 3.7中保證以LIFO的順序移除,在此以前不保證移除順序。
例如,下面是在python 3.5中的操做時(不保證順序):
>>> d {'three': 3, 'four': 4, 'two': 2, 'one': 1} >>> d.popitem() ('three', 3) >>> d.popitem() ('four', 4) >>> d.popitem() ('two', 2) >>> d.popitem() ('one', 1) >>> d.popitem() Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'popitem(): dictionary is empty'
經過d[key]
的方式檢索字典中的某個元素時,若是該元素不存在將報錯。使用get()方法能夠指定元素不存在時的默認返回值,而不報錯。而設置元素時,可用經過直接賦值的方式,也能夠經過setdefault()方法來爲不存在的值設置默認值。
重點在於元素是否存在於字典中。上面的幾種方法能在檢測元素是否存在時作出對應的操做,但字典做爲容器,也能夠直接用in
和not in
去測試元素的存在性。
>>> "one" in d True >>> "one3" in d False >>> "one3" not in d True
iter(d.keys())
前3個方法返回的是字典視圖對象,關於這個稍後再說。先看返回結果:
>>> d {'three': 3, 'four': 4, 'two': 2, 'one': 1} >>> d.keys() dict_keys(['three', 'four', 'two', 'one']) >>> list(d.keys()) ['three', 'four', 'two', 'one'] >>> d.values() dict_values([3, 4, 2, 1]) >>> d.items() dict_items([('three', 3), ('four', 4), ('two', 2), ('one', 1)])
iter(d)返回的是由key組成的可迭代對象。
>>> iter(d) <dict_keyiterator object at 0x7f0ab9c9c4f8> >>> for i in iter(d):print(i) ... three four two one
既然這些都返回key、value、item組成的"列表"對象(視圖對象),那麼能夠直接拿來迭代遍歷。
>>> for i in d.keys(): ... print(i) ... three four two one >>> for i in d.values(): ... print(i) ... 3 4 2 1 >>> for (key,value) in d.items(): ... print(key,"-->",value) ... three --> 3 four --> 4 two --> 2 one --> 1
keys()、values()、items()返回字典視圖對象。視圖對象中的數據會隨着原字典的改變而改變。若是知道關係型數據庫裏的視圖,這很容易理解。
>>> d {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> d.keys() dict_keys(['one', 'two', 'three', 'four']) >>> list(d.keys()) ['one', 'two', 'three', 'four']
字典視圖對象是可迭代對象,能夠用來一個個地生成對應數據,但它畢竟不是列表。若是須要獲得列表,只需使用list()方法構造便可。
>>> list(d.keys()) ['one', 'two', 'three', 'four']
由於字典試圖是可迭代對象,因此能夠進行測試存在性、迭代、遍歷等。
KEY in d.keys() for key in d.keys(): ... for value in d.values(): ... for (key, value) in d.items(): ...
字典的視圖對象有兩個函數:
>>> len(d.keys()) 4 >>> iter(d.keys()) <dict_keyiterator object at 0x000001F0A7D9A9F8>
注意,字典視圖對象是可迭代對象,但並非實際的列表,因此不能使用sort方法來排序,但可使用sorted()內置函數來排序(按照key進行排序)。
最後,視圖對象是隨原始字典動態改變的。修改原始字典,視圖也會改變。例如:
>>> d = {'one': 1, 'two': 2, 'three': 3, 'four': 4} >>> ks = d.keys() >>> del d["one"] >>> k dict_keys(['two', 'three', 'four'])
字典自身有迭代器,若是須要迭代key,則不須要使用keys()來間接迭代。因此下面是等價的:
for key in d: for key in d.keys()
關於字典解析,看幾個示例便可理解:
>>> d = {k:v for (k,v) in zip(["one","two","three"],[1,2,3])} >>> d {'one': 1, 'two': 2, 'three': 3} >>> d = {x : x ** 2 for x in [1,2,3,4]} >>> d {1: 1, 2: 4, 3: 9, 4: 16} >>> d = {x : None for x in "abcde"} >>> d {'a': None, 'b': None, 'c': None, 'd': None, 'e': None}