跟前面所講述的其它數(shù)據(jù)類(lèi)型類(lèi)似,字典也有一些方法。通過(guò)這些方法,能夠?qū)崿F(xiàn)對(duì)字典類(lèi)型數(shù)據(jù)的操作。這回可不是屠龍之技的。這些方法在編程實(shí)踐中經(jīng)常會(huì)用到。
拷貝,這個(gè)漢語(yǔ)是 copy 的音譯,標(biāo)準(zhǔn)的漢語(yǔ)翻譯是“復(fù)制”。我還記得當(dāng)初在學(xué) DOS 的時(shí)候,那個(gè)老師說(shuō)“拷貝”,搞得我暈頭轉(zhuǎn)向,他沒(méi)有說(shuō)英文的“copy”發(fā)音,而是用標(biāo)準(zhǔn)漢語(yǔ)說(shuō)“kao(三聲)bei(四聲)”,對(duì)于一直學(xué)習(xí)過(guò)英語(yǔ)、標(biāo)準(zhǔn)漢語(yǔ)和我家鄉(xiāng)方言的人來(lái)說(shuō),理解“拷貝”是有點(diǎn)困難的。誰(shuí)知道在編程界用的是音譯呢。
在一般的理解中,copy 就是將原來(lái)的東西再搞一份。但是,在 Python 里面(乃至于很多編程語(yǔ)言中),copy 可不是那么簡(jiǎn)單的。
>>> a = 5
>>> b = a
>>> b
5
這樣做,是不是就得到了兩個(gè) 5 了呢?表面上看似乎是,但是,不要忘記我在前面反復(fù)提到的:對(duì)象有類(lèi)型,變量無(wú)類(lèi)型,正是因著這句話(huà),變量其實(shí)是一個(gè)標(biāo)簽。不妨請(qǐng)出法寶:id()
,專(zhuān)門(mén)查看內(nèi)存中對(duì)象編號(hào)
>>> id(a)
139774080
>>> id(b)
139774080
果然,并沒(méi)有兩個(gè) 5,就一個(gè),只不過(guò)是貼了兩張標(biāo)簽而已。這種現(xiàn)象普遍存在于 Python 的多種數(shù)據(jù)類(lèi)型中。其它的就不演示了,就僅看看 dict 類(lèi)型。
>>> ad = {"name":"qiwsir", "lang":"Python"}
>>> bd = ad
>>> bd
{'lang': 'Python', 'name': 'qiwsir'}
>>> id(ad)
3072239652L
>>> id(bd)
3072239652L
是的,驗(yàn)證了。的確是一個(gè)對(duì)象貼了兩個(gè)標(biāo)簽。這是用賦值的方式,實(shí)現(xiàn)的所謂“假裝拷貝”。那么如果用 copy 方法呢?
>>> cd = ad.copy()
>>> cd
{'lang': 'Python', 'name': 'qiwsir'}
>>> id(cd)
3072239788L
果然不同,這次得到的 cd 是跟原來(lái)的 ad 不同的,它在內(nèi)存中另辟了一個(gè)空間。如果我嘗試修改 cd,就應(yīng)該對(duì)原來(lái)的 ad 不會(huì)造成任何影響。
>>> cd["name"] = "itdiffer.com"
>>> cd
{'lang': 'Python', 'name': 'itdiffer.com'}
>>> ad
{'lang': 'Python', 'name': 'qiwsir'}
真的是那樣,跟推理一模一樣。所以,要理解了“變量”是對(duì)象的標(biāo)簽,對(duì)象有類(lèi)型而變量無(wú)類(lèi)型,就能正確推斷出 Python 能夠提供的結(jié)果。
>>> bd
{'lang': 'Python', 'name': 'qiwsir'}
>>> bd["name"] = "laoqi"
>>> ad
{'lang': 'Python', 'name': 'laoqi'}
>>> bd
{'lang': 'Python', 'name': 'laoqi'}
這是又修改了 bd 所對(duì)應(yīng)的“對(duì)象”,結(jié)果發(fā)現(xiàn) ad 的“對(duì)象”也變了。
然而,事情沒(méi)有那么簡(jiǎn)單,看下面的,要仔細(xì)點(diǎn),否則就迷茫了。
>>> x = {"name":"qiwsir", "lang":["Python", "java", "c"]}
>>> y = x.copy()
>>> y
{'lang': ['Python', 'java', 'c'], 'name': 'qiwsir'}
>>> id(x)
3072241012L
>>> id(y)
3072241284L
y 是從 x 拷貝過(guò)來(lái)的,兩個(gè)在內(nèi)存中是不同的對(duì)象。
>>> y["lang"].remove("c")
為了便于理解,盡量使用短句子,避免用很長(zhǎng)很長(zhǎng)的復(fù)合句。在 y 所對(duì)應(yīng)的 dict 對(duì)象中,鍵"lang"的值是一個(gè)列表,為['Python', 'java', 'c'],這里用 remove()
這個(gè)列表方法刪除其中的一個(gè)元素"c"。刪除之后,這個(gè)列表變?yōu)椋篬'Python', 'java']
>>> y
{'lang': ['Python', 'java'], 'name': 'qiwsir'}
果然不出所料。那么,那個(gè)x所對(duì)應(yīng)的字典中,這個(gè)列表變化了嗎?應(yīng)該沒(méi)有變化。因?yàn)榘凑涨懊嫠v的,它是另外一個(gè)對(duì)象,兩個(gè)互不干擾。
>>> x
{'lang': ['Python', 'java'], 'name': 'qiwsir'}
是不是有點(diǎn)出乎意料呢?我沒(méi)有作弊哦。你如果不信,就按照操作自己在交互模式中試試,是不是能夠得到這個(gè)結(jié)果呢?這是為什么?
但是,如果要操作另外一個(gè)鍵值對(duì):
>>> y["name"] = "laoqi"
>>> y
{'lang': ['python', 'java'], 'name': 'laoqi'}
>>> x
{'lang': ['python', 'java'], 'name': 'qiwsir'}
前面所說(shuō)的原理是有效的,為什么到值是列表的時(shí)候就不奏效了呢?
要破解這個(gè)迷局還得用 id()
>>> id(x)
3072241012L
>>> id(y)
3072241284L
x,y 對(duì)應(yīng)著兩個(gè)不同對(duì)象,的確如此。但這個(gè)對(duì)象(字典)是由兩個(gè)鍵值對(duì)組成的。其中一個(gè)鍵的值是列表。
>>> id(x["lang"])
3072243276L
>>> id(y["lang"])
3072243276L
發(fā)現(xiàn)了這樣一個(gè)事實(shí),列表是同一個(gè)對(duì)象。
但是,作為字符串為值得那個(gè)鍵值對(duì),是分屬不同對(duì)象。
>>> id(x["name"])
3072245184L
>>> id(y["name"])
3072245408L
這個(gè)事實(shí),就說(shuō)明了為什么修改一個(gè)列表,另外一個(gè)也跟著修改;而修改一個(gè)的字符串,另外一個(gè)不跟隨的原因了。
但是,似乎還沒(méi)有解開(kāi)深層的原因。深層的原因,這跟 Python 存儲(chǔ)的數(shù)據(jù)類(lèi)型特點(diǎn)有關(guān),Python 只存儲(chǔ)基本類(lèi)型的數(shù)據(jù),比如 int,str,對(duì)于不是基礎(chǔ)類(lèi)型的,比如剛才字典的值是列表,Python 不會(huì)在被復(fù)制的那個(gè)對(duì)象中重新存儲(chǔ),而是用引用的方式,指向原來(lái)的值。如果讀者沒(méi)有明白這句話(huà)的意思,我就只能說(shuō)點(diǎn)通俗的了(我本來(lái)不想說(shuō)通俗的,裝著自己有學(xué)問(wèn)),Python 在所執(zhí)行的復(fù)制動(dòng)作中,如果是基本類(lèi)型的數(shù)據(jù),就在內(nèi)存中重新建個(gè)窩,如果不是基本類(lèi)型的,就不新建窩了,而是用標(biāo)簽引用原來(lái)的窩。這也好理解,如果比較簡(jiǎn)單,隨便建立新窩簡(jiǎn)單;但是,如果對(duì)象太復(fù)雜了,就別費(fèi)勁了,還是引用一下原來(lái)的省事。(這么講有點(diǎn)忽悠了)。
所以,在編程語(yǔ)言中,把實(shí)現(xiàn)上面那種拷貝的方式稱(chēng)之為“淺拷貝”。顧名思義,沒(méi)有解決深層次問(wèn)題。言外之意,還有能夠解決深層次問(wèn)題的方法嘍。
的確是,在 Python 中,有一個(gè)“深拷貝”(deep copy)。不過(guò),要用下一 import
來(lái)導(dǎo)入一個(gè)模塊。這個(gè)東西后面會(huì)講述,前面也遇到過(guò)了。
>>> import copy
>>> z = copy.deepcopy(x)
>>> z
{'lang': ['python', 'java'], 'name': 'qiwsir'}
用 copy.deepcopy()
深拷貝了一個(gè)新的副本,看這個(gè)函數(shù)的名字就知道是深拷貝(deepcopy)。用上面用過(guò)的武器 id()來(lái)勘察一番:
>>> id(x["lang"])
3072243276L
>>> id(z["lang"])
3072245068L
果然是另外一個(gè)“窩”,不是引用了。如果按照這個(gè)結(jié)果,修改其中一個(gè)列表中的元素,應(yīng)該不影響另外一個(gè)了。
>>> x
{'lang': ['Python', 'java'], 'name': 'qiwsir'}
>>> x["lang"].remove("java")
>>> x
{'lang': ['Python'], 'name': 'qiwsir'}
>>> z
{'lang': ['Python', 'java'], 'name': 'qiwsir'}
果然如此。再試試,才過(guò)癮呀。
>>> x["lang"].append("c++")
>>> x
{'lang': ['Python', 'c++'], 'name': 'qiwsir'}
這就是所謂淺拷貝和深拷貝。
在交互模式中,用 help 是一個(gè)很好的習(xí)慣
>>> help(dict.clear)
clear(...)
D.clear() -> None. Remove all items from D.
這是一個(gè)清空字典中所有元素的操作。
>>> a = {"name":"qiwsir"}
>>> a.clear()
>>> a
{}
這就是 clear
的含義,將字典清空,得到的是“空”字典。這個(gè)上節(jié)說(shuō)的 del
有著很大的區(qū)別。del
是將字典刪除,內(nèi)存中就沒(méi)有它了,不是為“空”。
>>> del a
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
果然刪除了。
另外,如果要清空一個(gè)字典,還能夠使用 a = {}
這種方法,但這種方法本質(zhì)是將變量 a 轉(zhuǎn)向了{}
這個(gè)對(duì)象,那么原來(lái)的呢?原來(lái)的成為了斷線(xiàn)的風(fēng)箏。這樣的東西在 Python 中稱(chēng)之為垃圾,而且 Python 能夠自動(dòng)的將這樣的垃圾回收。編程者就不用關(guān)心它了,反正 Python 會(huì)處理了。
get 的含義是:
get(...)
D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.
注意這個(gè)說(shuō)明中,“if k in D”,就返回其值,否則...(等會(huì)再說(shuō))。
>>> d
{'lang': 'python'}
>>> d.get("lang")
'python'
dict.get()
就是要得到字典中某個(gè)鍵的值,不過(guò),它不是那么“嚴(yán)厲”罷了。因?yàn)轭?lèi)似獲得字典中鍵的值得方法,上節(jié)已經(jīng)有過(guò),如 d['lang']
就能得到對(duì)應(yīng)的值"Python"
,可是,如果要獲取的鍵不存在,如:
>>> print d.get("name")
None
>>> d["name"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'name'
這就是 dict.get()
和 dict['key']
的區(qū)別。
前面有一個(gè)半句話(huà),如果鍵不在字典中,會(huì)返回 None,這是一種情況。還可以這樣:
>>> d = {"lang":"Python"}
>>> newd = d.get("name",'qiwsir')
>>> newd
'qiwsir'
>>> d
{'lang': 'Python'}
以 d.get("name",'qiwsir')
的方式,如果不能得到鍵"name"的值,就返回后面指定的值"qiwsir"。這就是文檔中那句話(huà):D[k] if k in D, else d.
的含義。這樣做,并沒(méi)有影響原來(lái)的字典。
另外一個(gè)跟 get 在功能上有相似地方的 D.setdefault(k)
,其含義是:
setdefault(...)
D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D
首先,它要執(zhí)行 D.get(k,d)
,就跟前面一樣了,然后,進(jìn)一步執(zhí)行另外一個(gè)操作,如果鍵k不在字典中,就在字典中增加這個(gè)鍵值對(duì)。當(dāng)然,如果有就沒(méi)有必要執(zhí)行這一步了。
>>> d
{'lang': 'Python'}
>>> d.setdefault("lang")
'Python'
在字典中,有"lang"這個(gè)鍵,那么就返回它的值。
>>> d.setdefault("name","qiwsir")
'qiwsir'
>>> d
{'lang': 'Python', 'name': 'qiwsir'}
沒(méi)有"name"這個(gè)鍵,于是返回 d.setdefault("name","qiwsir")
指定的值"qiwsir",并且將鍵值對(duì)'name':"qiwsir"
添加到原來(lái)的字典中。
如果這樣操作:
>>> d.setdefault("web")
什么也沒(méi)有返回嗎?不是,返回了,只不過(guò)沒(méi)有顯示出來(lái),如果你用 print 就能看到了。因?yàn)檫@里返回的是一個(gè) None.不妨查看一下那個(gè)字典。
>>> d
{'lang': 'Python', 'web': None, 'name': 'qiwsir'}
是不是鍵"web"的值成為了 None
這個(gè)標(biāo)題中列出的是三組 dict 的函數(shù),并且這三組有相似的地方。在這里詳細(xì)講述第一組,其余兩組,我想憑借讀者的聰明智慧是不在話(huà)下的。
>>> help(dict.items)
items(...)
D.items() -> list of D's (key, value) pairs, as 2-tuples
這種方法是慣用的伎倆了,只要在交互模式中鼓搗一下,就能得到幫助信息。從中就知道 D.items()
能夠得到一個(gè)關(guān)于字典的列表,列表中的元素是由字典中的鍵和值組成的元組。例如:
>>> dd = {"name":"qiwsir", "lang":"python", "web":"www.itdiffer.com"}
>>> dd_kv = dd.items()
>>> dd_kv
[('lang', 'Python'), ('web', 'www.itdiffer.com'), ('name', 'qiwsir')]
顯然,是有返回值的。這個(gè)操作,在后面要講到的循環(huán)中,將有很大的作用。
跟 items
類(lèi)似的就是 iteritems
,看這個(gè)詞的特點(diǎn),是由 iter 和 items 拼接而成的,后部分 items 就不用說(shuō)了,肯定是在告訴我們,得到的結(jié)果跟 D.items()
的結(jié)果類(lèi)似。是的,但是,還有一個(gè) iter 是什么意思?在《列表(2)中,我提到了一個(gè)詞“iterable”,它的含義是“可迭代的”,這里的 iter 是指的名詞 iterator 的前部分,意思是“迭代器”。合起來(lái),"iteritems"的含義就是:
iteritems(...)
D.iteritems() -> an iterator over the (key, value) items of D
你看,學(xué)習(xí) Python 不是什么難事,只要充分使用幫助文檔就好了。這里告訴我們,得到的是一個(gè)“迭代器”(關(guān)于什么是迭代器,以及相關(guān)的內(nèi)容,后續(xù)會(huì)詳細(xì)講述),這個(gè)迭代器是關(guān)于“D.items()”的。看個(gè)例子就明白了。
>>> dd
{'lang': 'Python', 'web': 'www.itdiffer.com', 'name': 'qiwsir'}
>>> dd_iter = dd.iteritems()
>>> type(dd_iter)
<type 'dictionary-itemiterator'>
>>> dd_iter
<dictionary-itemiterator object at 0xb72b9a2c>
>>> list(dd_iter)
[('lang', 'Python'), ('web', 'www.itdiffer.com'), ('name', 'qiwsir')]
得到的 dd_iter 的類(lèi)型,是一個(gè)'dictionary-itemiterator'類(lèi)型,不過(guò)這種迭代器類(lèi)型的數(shù)據(jù)不能直接輸出,必須用 list()
轉(zhuǎn)換一下,才能看到里面的真面目。
另外兩組,含義跟這個(gè)相似,只不過(guò)是得到 key 或者 value。下面僅列舉一下例子,具體內(nèi)容,讀者可以自行在交互模式中看文檔。
>>> dd
{'lang': 'Python', 'web': 'www.itdiffer.com', 'name': 'qiwsir'}
>>> dd.keys()
['lang', 'web', 'name']
>>> dd.values()
['Python', 'www.itdiffer.com', 'qiwsir']
這里先交代一句,如果要實(shí)現(xiàn)對(duì)鍵值對(duì)或者鍵或者值的循環(huán),用迭代器的效率會(huì)高一些。對(duì)這句話(huà)的理解,在后面會(huì)給大家進(jìn)行詳細(xì)分析。
在《列表(3)》中,有關(guān)于刪除列表中元素的函數(shù) pop
和 remove
,這兩個(gè)的區(qū)別在于 list.remove(x)
用來(lái)刪除指定的元素,而 list.pop([i])
用于刪除指定索引的元素,如果不提供索引值,就默認(rèn)刪除最后一個(gè)。
在字典中,也有刪除鍵值對(duì)的函數(shù)。
pop(...)
D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError is raised
D.pop(k[,d])
是以字典的鍵為參數(shù),刪除指定鍵的鍵值對(duì),當(dāng)然,如果輸入對(duì)應(yīng)的值也可以,那個(gè)是可選的。
>>> dd
{'lang': 'Python', 'web': 'www.itdiffer.com', 'name': 'qiwsir'}
>>> dd.pop("name")
'qiwsir'
要?jiǎng)h除指定鍵"name",返回了其值"qiwsir"。這樣,在原字典中,“'name':'qiwsir'”這個(gè)鍵值對(duì)就被刪除了。
>>> dd
{'lang': 'Python', 'web': 'www.itdiffer.com'}
值得注意的是,pop 函數(shù)中的參數(shù)是不能省略的,這跟列表中的那個(gè) pop 有所不同。
>>> dd.pop()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pop expected at least 1 arguments, got 0
如果要?jiǎng)h除字典中沒(méi)有的鍵值對(duì),也會(huì)報(bào)錯(cuò)。
>>> dd.pop("name")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'name'
有意思的是 D.popitem()
倒是跟 list.pop()
有相似之處,不用寫(xiě)參數(shù)(list.pop 是可以不寫(xiě)參數(shù)),但是,D.popitem()
不是刪除最后一個(gè),前面已經(jīng)交代過(guò)了,dict 沒(méi)有順序,也就沒(méi)有最后和最先了,它是隨機(jī)刪除一個(gè),并將所刪除的返回。
popitem(...)
D.popitem() -> (k, v), remove and return some (key, value) pair as a
2-tuple; but raise KeyError if D is empty.
如果字典是空的,就要報(bào)錯(cuò)了
>>> dd
{'lang': 'Python', 'web': 'www.itdiffer.com'}
>>> dd.popitem()
('lang', 'Python')
>>> dd
{'web': 'www.itdiffer.com'}
成功地刪除了一對(duì),注意是隨機(jī)的,不是刪除前面顯示的最后一個(gè)。并且返回了刪除的內(nèi)容,返回的數(shù)據(jù)格式是 tuple
>>> dd.popitems()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'popitems'
錯(cuò)了?!注意看提示信息,沒(méi)有那個(gè)...,哦,果然錯(cuò)了。注意是 popitem,不要多了 s,前面的 D.items()
中包含 s,是復(fù)數(shù)形式,說(shuō)明它能夠返回多個(gè)結(jié)果(多個(gè)元組組成的列表),而在 D.popitem()
中,一次只能隨機(jī)刪除一對(duì)鍵值對(duì),并以一個(gè)元組的形式返回,所以,要單數(shù)形式,不能用復(fù)數(shù)形式了。
>>> dd.popitem()
('web', 'www.itdiffer.com')
>>> dd
{}
都刪了,現(xiàn)在那個(gè)字典成空的了。如果再刪,會(huì)怎么樣?
>>> dd.popitem()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'popitem(): dictionary is empty'
報(bào)錯(cuò)信息中明確告知,字典已經(jīng)是空的了,沒(méi)有再供刪的東西了。
update()
,看名字就猜測(cè)到一二了,是不是更新字典內(nèi)容呢?的確是。
update(...)
D.update([E, ]**F) -> None. Update D from dict/iterable E and F.
If E present and has a .keys() method, does: for k in E: D[k] = E[k]
If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
In either case, this is followed by: for k in F: D[k] = F[k]
不過(guò),看樣子這個(gè)函數(shù)有點(diǎn)復(fù)雜。不要著急,通過(guò)實(shí)驗(yàn)可以一點(diǎn)一點(diǎn)鼓搗明白的。
首先,這個(gè)函數(shù)沒(méi)有返回值,或者說(shuō)返回值是 None,它的作用就是更新字典。其參數(shù)可以是字典或者某種可迭代的數(shù)據(jù)類(lèi)型。
>>> d1 = {"lang":"python"}
>>> d2 = {"song":"I dreamed a dream"}
>>> d1.update(d2)
>>> d1
{'lang': 'Python', 'song': 'I dreamed a dream'}
>>> d2
{'song': 'I dreamed a dream'}
這樣就把字典 d2 更新入了 d1 那個(gè)字典,于是 d1 中就多了一些內(nèi)容,把 d2 的內(nèi)容包含進(jìn)來(lái)了。d2 當(dāng)然還存在,并沒(méi)有受到影響。
還可以用下面的方法更新:
>>> d2
{'song': 'I dreamed a dream'}
>>> d2.update([("name","qiwsir"), ("web","itdiffer.com")])
>>> d2
{'web': 'itdiffer.com', 'name': 'qiwsir', 'song': 'I dreamed a dream'}
列表的元組是鍵值對(duì)。
這個(gè)函數(shù)的功能是判斷字典中是否存在某個(gè)鍵
has_key(...)
D.has_key(k) -> True if D has a key k, else False
跟前一節(jié)中遇到的 k in D
類(lèi)似。
>>> d2
{'web': 'itdiffer.com', 'name': 'qiwsir', 'song': 'I dreamed a dream'}
>>> d2.has_key("web")
True
>>> "web" in d2
True
關(guān)于 dict 的函數(shù),似乎不少。但是,不用著急,也不用擔(dān)心記不住,因?yàn)楦静恍枰洃?。只要?huì)用搜索即可。
總目錄 | 上節(jié):字典(1) | 下節(jié):集合(1)
如果你認(rèn)為有必要打賞我,請(qǐng)通過(guò)支付寶:qiwsir@126.com,不勝感激。