python字典類型

字典類型簡介

字典(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中最靈活的兩種數據類型。

可是:

  1. dict的元素檢索、增刪改速度快,不會隨着元素增多、減小而改變。但缺點是內存佔用大
  2. list的元素檢索、增刪改速度隨着元素增多會愈來愈慢(固然實際影響並無多大),可是內存佔用小

換句話說,dict是空間換時間,list是時間換空間。

其實從dict和list的數據結構上很容易能夠看出dict要比list佔用的內存大。不考慮存儲元素的實際數據空間,list存儲每一個元素只需一個指針共8字節(64位機器)便可保存,而dict至少須要24字節(64位機器),不只如此,hash表結構中每一個hash桶基本上會空餘1/3以上的空間。

構造字典

有幾種構造字典的方式:

  1. 使用大括號包圍
  2. 使用dict()構造方法,dict()構造有3種方式:
    • dict(key=value)
    • dict(DICT)
    • dict(iterable),其中iterable的每一個元素必須是兩元素的數據對象,例如("one",1)["two",2]
    • 後兩種均可以結合第一種方式
  3. 使用dict對象的fromkey()方法
  4. 使用dict對象的copy()方法
  5. 字典解析的方式。這個在後文再解釋
>>> 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

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()方法來爲不存在的值設置默認值。

重點在於元素是否存在於字典中。上面的幾種方法能在檢測元素是否存在時作出對應的操做,但字典做爲容器,也能夠直接用innot in去測試元素的存在性。

>>> "one" in d
True
>>> "one3" in d
False
>>> "one3" not in d
True

迭代和dict視圖

  • keys()返回字典中全部的key組成的視圖對象;
  • values()返回字典中全部value組成的視圖對象;
  • items()返回字典中全部(key,value)元組對組成的視圖對象;
  • iter(d)函數返回字典中全部key組成的可迭代對象。等價於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

dict視圖對象

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(obj_view):返回視圖對象的長度
  • iter(obj_view):返回視圖對象對應的可迭代對象
>>> 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}
相關文章
相關標籤/搜索