本文為《爬著學(xué)Python》系列第九篇文章。
從現(xiàn)在開始算是要進(jìn)入“真刀真槍”的Python學(xué)習(xí)了。之所以這么說返敬,是因為學(xué)完P(guān)ython進(jìn)階模塊以后,基本上就具備了一定的Python編程能力了寥院【⒃可以說我們可以用不那么聰明的方法實現(xiàn)我們要實現(xiàn)的一切功能,而更高級的技巧則會在Python精進(jìn)中介紹。
本文的目的在于介紹Python的內(nèi)置簡單數(shù)據(jù)結(jié)構(gòu)凛澎,重點是列表list和字典dict霹肝,也會簡單介紹元祖tuple和集合set。我所說的“簡單”塑煎,不是指這些數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)起來難度低沫换、或者說功能比較單一(其實相反),而是說這些數(shù)據(jù)結(jié)構(gòu)在Python中最常用最铁,形式最簡單讯赏,而我們要介紹的又是他們的一些簡單用法。
列表 list
任何了解過數(shù)據(jù)結(jié)構(gòu)的人都應(yīng)該明白冷尉,列表永遠(yuǎn)是應(yīng)用最廣泛漱挎,功能最多樣化的數(shù)據(jù)結(jié)構(gòu),這樣的數(shù)據(jù)結(jié)構(gòu)是不應(yīng)該稱之為“簡單”的网严。Python中的列表尤是如此。Python中的列表是一種分離式動態(tài)順序表嗤无,可以實現(xiàn)非常多的數(shù)據(jù)結(jié)構(gòu)震束,如可以直接作為棧,可以實現(xiàn)隊列当犯,可以直接表示完全二叉樹所以可以作為堆使用垢村,可以嵌套表示一般樹形結(jié)構(gòu),用自定義類進(jìn)行簡單改造能更加高效地實現(xiàn)以上的以及其他許多功能……
在介紹完列表的基礎(chǔ)知識以后嚎卫,會對上面這些結(jié)構(gòu)的實現(xiàn)進(jìn)行簡單示例嘉栓。
創(chuàng)建列表
Python中對于列表的使用就像普通變量一樣,不需要變量聲明拓诸,直接將列表元素用方括號[]
框起來進(jìn)行賦值即可侵佃。例如
>>> sample1 = [1, '2', 'woo!']
需要注意的是Python的列表可以同時接受不同類型的元素,非常寬容奠支,無論是數(shù)字馋辈、布爾值還是字符串、其他變量倍谜、其他數(shù)據(jù)結(jié)構(gòu)迈螟,甚至是函數(shù),只要是對象尔崔,都可以作為列表元素保存在列表中答毫。這也給Python列表操作帶來了各種可能性,應(yīng)用場景中充分發(fā)揮想象力可以使代碼非常優(yōu)雅簡潔季春。
另外洗搂,我們可以對一切可迭代對象使用內(nèi)置函數(shù)list()
來獲得一個新的列表(在下一篇自定義函數(shù)文中,我們會仔細(xì)討論為什么說這是個新列表)。
>>> sample2 = list(sample1)
對于一般可迭代對象蚕脏,我們還可以用列表生成器來完成生成列表的工作(關(guān)于迭代器和生成器會在更靠后的文章中進(jìn)行介紹)侦副。
>>> sample3 = [i for i in range(10) if i%2==0]
至此我們介紹了三種新建列表的方法:
- 一般賦值語句
- list() 函數(shù)轉(zhuǎn)換可迭代對象
- 列表生成器
在文末介紹元祖以后,我們會認(rèn)識到其實這些方法都是殊途同歸的驼鞭。
列表的一些基本操作
我們提到過Python的內(nèi)置列表是一種分離式動態(tài)順序表秦驯,我們可以先不用知道這是什么意思,只要知道這代表Python列表是可索引的而且可以幾乎無限制地往里面插入數(shù)據(jù)(如果以后確定開數(shù)據(jù)結(jié)構(gòu)模塊內(nèi)容挣棕,會介紹實現(xiàn)方式)译隘。
索引和切片
所謂的索引,可以理解為Python的列表元素按順序有自己的“號碼“洛心,我們可以通過這個號碼來直接訪問Python的列表元素固耘。
>>> sample1 = [1, '2', 'woo!']
>>> elem1 = sample1[2]
>>> elem1
'woo!'
注意到我們可以把這個列表元素對象通過賦值直接傳給變量。而且词身,我們索引的號碼是[2]
厅目,得到的卻是列表的第三個元素。原因是法严,列表元素的索引是從0
開始的损敷,它代表列表的第一個元素。而出現(xiàn)這種現(xiàn)象的原因深啤,是因為“傳統(tǒng)”拗馒。在許多較早出現(xiàn)的語言如C語言中,定義列表變量類型時需要聲明溯街,聲明時有時候需要聲明它的容量诱桂,這時候一般會寫成int a[10]
,這表示聲明一個能保存10個整數(shù)的列表呈昔。這時候再去試圖用a[10]
來訪問其中的元素顯然是會產(chǎn)生歧義的挥等,事實上a[10]
在C中代表這個列表本身。為了消除歧義也為了計算機(jī)處理方便堤尾,從0
開始的索引就應(yīng)運(yùn)而生了触菜,這其實也算是非常巧妙的設(shè)計,是工程師智慧的結(jié)晶哀峻。
在Python中雖然已經(jīng)沒有這樣做的必要了涡相,但還是遵循了這一傳統(tǒng),這也是為了方便早期的程序員更快地適應(yīng)這種新語言剩蟀。因此我們在以后也可能會看到有些Python面向?qū)ο蟪绦蛟O(shè)計會把列表的第一個元素設(shè)置為a[0]=0
催蝗,然后用一個變量來保存真正在列表后面添加的元素個數(shù),可以先定義currentsize = 0
每次插入數(shù)據(jù)就currentsize += 1
育特,或者每次需要訪問長度時currentsize = len(a)-1
丙号。這是為了讓列表更加符合邏輯而且在某些條件判斷處理上有許多優(yōu)勢先朦,否則也可以用a[0]
來保存列表的真實長度。GitHub上有一部分Python程序員會采用這種方法犬缨,初次接觸不要奇怪人家為什么要這么做喳魏。
好吧,說了這么多只是為了加深印象怀薛,列表元素的索引是從0
開始的刺彩。另外一點要說的是,Python列表接受負(fù)數(shù)作為參數(shù)從列表尾端訪問元素枝恋,不一樣的是创倔,列表尾端第一個元素的索引是-1
,后面以此類推焚碌。
>>> sample1 = [1, '2', 'woo!']
>>> elem1 = sample1[-2]
>>> elem1
'2'
接下來講列表元素的切片。在知道了索引的基礎(chǔ)上十电,切片非常好理解知押。索引是訪問列表中的一個元素,而切片則是訪問列表中的一段元素鹃骂。
>>> sample3 = [i for i in range(10) if i%2==0]
>>> slice1 = sample3[1:3]
>>> print(slice1)
[2, 4]
>>> print(type(slice1))
<class 'list'>
注意到切片返回的是一個列表台盯。列表切片可以接受兩個參數(shù),一個作為起點偎漫,一個作為終點爷恳,其中終點不包括在切片范圍中有缆。這種特性是不是有點像range函數(shù)呢象踊?其實,列表切片還有一些其他技巧棚壁。
>>> sample3 = [i for i in range(10) if i%2==0]
>>> sample3[1:]
[2, 4, 6, 8]
>>> sample3[:-2]
[0, 2, 4]
>>> sample3[::-1]
[8, 6, 4, 2, 0]
注意到
- 切片時可以略去某端的參數(shù)杯矩,代表取到這一端的列表盡頭。
- 切片時也可以用負(fù)數(shù)當(dāng)參數(shù)袖外,規(guī)則和索引類似史隆。
- 切片可以和range函數(shù)一樣接受第三個參數(shù)作為步長,這個步長也可以是負(fù)數(shù)曼验,
[::-1]
這樣的索引方式可以方便地將列表倒轉(zhuǎn)泌射。
在以上索引和切片的操作中還有一點要提的就是,如果索引的標(biāo)簽超出列表范圍鬓照,程序就會拋出IndexError索引錯誤熔酷。
列表的加法和乘法
Python中的列表可以使用加法和乘法。那么為什么不能用減法和除法呢豺裆?因為索引和切片可以看作是使列表縮短的操作拒秘,這完成了減法和除法應(yīng)該完成的任務(wù)。
Python列表的加法必須是兩個列表進(jìn)行相加,效果是將兩個列表連接起來躺酒。
>>> sample1 = [1, '2', 'woo!']
>>> sample3 = [i for i in range(10) if i%2==0]
>>> sample1 + sample3
[1, '2', 'woo!', 0, 2, 4, 6, 8]
Python列表的乘法必須是列表乘以一個整數(shù)押蚤,效果是將該列表重復(fù)。
>>> sample1 = [1, '2', 'woo!']
>>> sample1 * 2
[1, '2', 'woo!', 1, '2', 'woo!']
這兩個操作比較簡單羹应,不多贅述了揽碘。自此我們掌握了一般的使列表長度變化的方法。接下來我們需要認(rèn)識的是Python列表的一些簡單的其他內(nèi)置方法量愧。
列表的一些內(nèi)置方法
這里涉及的都是一些簡單內(nèi)置方法钾菊,包括insert,len偎肃,append煞烫,pop,reverse累颂,sort滞详, clear。
insert和len
insert
方法能夠?qū)⑿略夭迦氲搅斜淼闹付ㄎ恢梦闪螅邮軆蓚€參數(shù)捕儒,第一個是待插入的元素所插入位置的索引,第二個是該元素對象肛搬。
>>> sample3 = [i for i in range(10) if i%2==0]
>>> sample3
[0, 2, 4, 6, 8]
>>> sample3.insert(3, 1)
>>> sample3
[0, 2, 4, 1, 6, 8]
注意到插入時蛇捌,將新元素插入要插入的索引位置后,從該位置起往后的列表元素都依次往后推了一格赫编,索引加一巡蘸。值得一提的是insert方法的第一個參數(shù)給定插入索引位置一樣可以是負(fù)數(shù),但是使用得不多擂送。由于會把該位置原來的元素往后擠悦荒,所以即使是用-1當(dāng)作參數(shù),新元素在操作結(jié)束后是列表的倒數(shù)第二個元素并不是倒數(shù)第一個嘹吨。
len
方法會返回列表的元素個數(shù)搬味,或者說叫列表的長度。
>>> sample3 = [i for i in range(10) if i%2==0]
>>> len(sample3)
5
>>> sample3.insert(3, 1)
>>> len(sample3)
6
len
方法堪稱是列表操作中最常用的方法蟀拷,簡單實用碰纬。
在這里再介紹一個不是很常用但有時也有奇效的count
方法,它接收一個參數(shù)问芬,返回該參數(shù)在列表中出現(xiàn)的次數(shù)悦析。這里只是為了和len方法進(jìn)行區(qū)分所以就不作演示了。
append和pop
append
方法可以在列表尾端插入元素愈诚,彌補(bǔ)了insert函數(shù)的不足她按。而且遠(yuǎn)遠(yuǎn)不只是這樣牛隅,append操作的速度要比insert快上非常多。原因也是顯而易見的酌泰,append操作不會擠到列表中的元素所以不用更改其他元素的索引媒佣。對應(yīng)的pop
函數(shù)會刪除列表尾端的元素,并且返回該元素對象陵刹。
>>> sample3 = [i for i in range(10) if i%2==0]
>>> sample3
[0, 2, 4, 6, 8]
>>> sample3.append(1)
>>> sample3
[0, 2, 4, 6, 8, 1]
>>> sample3.pop()
1
>>> sample3
[0, 2, 4, 6, 8]
之所以要把這兩個方法放在一起講默伍,是因為這兩個方法的存在使得Python列表天然地可以直接作為棧使用。棧是一種后進(jìn)先出的緩存結(jié)構(gòu)衰琐,在計算機(jī)中應(yīng)用非常普遍也糊。諸如遞歸、函數(shù)嵌套等羡宙,都是通過棧實現(xiàn)的狸剃。如果以后開了數(shù)據(jù)結(jié)構(gòu)板塊我們會詳細(xì)介紹棧的強(qiáng)大。
reverse狗热,sort和clear
reverse
方法會將列表反轉(zhuǎn)钞馁。sort
方法可以對列表元素進(jìn)行遞增的排序,加入?yún)?shù)reverse=True
則可以進(jìn)行遞減的排序匿刮。clear
方法會清空列表元素僧凰。
>>> sample3 = [i for i in range(10) if i%2==0]
>>> sample3
[0, 2, 4, 6, 8]
>>> sample3.reverse()
>>> sample3
[8, 6, 4, 2, 0]
>>> sample3.sort()
>>> sample3
[0, 2, 4, 6, 8]
>>> sample3.clear()
>>> sample3
[]
在以上涉及到改變列表長度的操作中, Python的處理辦法是容量不足時列表等量擴(kuò)容熟丸,但是容量冗余時不會自動清除多余的容量训措。還是那句話,如果仔細(xì)討論P(yáng)ython數(shù)據(jù)結(jié)構(gòu)的話光羞,會對相關(guān)內(nèi)容進(jìn)行詳細(xì)說明绩鸣。
列表是“可變”對象
介紹完列表的一些基本操作以后,我們要講一個比較重要的知識點狞山。這是Python可變對象的一些特性全闷,是面試時幾乎必然會涉及的內(nèi)容叉寂。我們在此處先簡單介紹一下萍启,在下一篇文章自定義函數(shù)相關(guān)內(nèi)容中會更加仔細(xì)地介紹Python中可變對象的特性為什么會這么重要。
首先我們要明確的一點是屏鳍,列表是可變對象勘纯。在上面我們介紹過的一系列操作,都是對列表對象本身進(jìn)行操作钓瞭。列表應(yīng)該是我在本專題中第一次介紹可變對象的概念驳遵。我們沒接觸過可變對象沒關(guān)系,我們接觸過不可變對象山涡。
>>> a = 3
>>> b = a
>>> a = a + 1
>>> print(b)
3
在上面的操作中我們對a賦值為3堤结,再將a的值賦給b唆迁,再將a的值加1,這時我們輸出b的值竞穷,b還是3唐责。這一切看上去是理所當(dāng)然的。那我們試一下對列表進(jìn)行這樣的操作呢瘾带?
>>> a = [3]
>>> b = a
>>> a = a + [1]
>>> print(a)
[3, 1]
>>> print(b)
[3, 1]
在上面的操作中我們對a賦值為單一元素的列表[3]
鼠哥,再將a的值賦給b,再將列表a后面接上一個新列表[1]
看政。根據(jù)剛才我們介紹過的列表操作可知朴恳,現(xiàn)在列表a應(yīng)該變成了[3, 1]
。試一下輸出a允蚣,確實是這樣的于颖。但這時我們輸出列表b,b還會是[3]
嗎嚷兔?事實上不是的恍飘,現(xiàn)在列表b也變成了[3, 1]
。
這就是這個知識點重要的原因谴垫,我們沒有對列表b進(jìn)行操作章母,可是它卻改變了。如果在實際項目中經(jīng)常出現(xiàn)這樣的現(xiàn)象翩剪,那恐怕bug多得永遠(yuǎn)也清不完乳怎。這要求我們要時刻清楚地意識到Python對象的特性。這里先簡單說一下會發(fā)生這種現(xiàn)象的原因前弯,具體的細(xì)節(jié)以及正確的操作方法在下一篇文章會重點介紹蚪缀。
簡單來說就是對于可變對象,它的許多方法會修改這個對象本身恕出,而對于不可變對象則是重新創(chuàng)建一個新對象询枚。我們將值為3的a加1,事實上是將a指向了一個值等于3+1的新對象浙巫。而我們在列表[3]
后面接上列表[1]
金蜀,它則是把列表[1]
中的元素依次放進(jìn)列表[3]
的尾端。這兩種操作的區(qū)別是的畴,前者創(chuàng)建了新對象而后者沒有渊抄,后者只是對原對象進(jìn)行修改。因此如果a和b都是指向這個列表丧裁,那么這個列表變動時护桦,a和b都會同時受到牽連。
這種特性要求我們分辨清楚煎娇,哪些操作是針對可變對象本身的二庵,哪些操作是將可變對象作為參數(shù)參與運(yùn)算而不進(jìn)行修改的贪染。如果我們不能合理地規(guī)避那些不應(yīng)該出現(xiàn)的修改,那么將會有無盡的奇怪bug出現(xiàn)在我們的程序中催享。而分辨方法比較簡單抑进,就和之前我們分辨函數(shù)變量和函數(shù)對象類似。如果列表對象出現(xiàn)在賦值語句的左邊睡陪,可以認(rèn)為是對列表變量進(jìn)行修改寺渗,如果在右邊則要分情況,如切片和索引就只訪問列表但不做修改兰迫,但是像resort方法pop方法就會修改列表了信殊。
相信看到這里你大概能理解為什么我把標(biāo)題取為“簡單數(shù)據(jù)結(jié)構(gòu)”,單是列表汁果,在絕大部分內(nèi)容我們都是淺嘗輒止的情況下涡拘,已經(jīng)是非常大的篇幅了。我只能淺顯地介紹一下相關(guān)內(nèi)容据德,更多內(nèi)容在后面的學(xué)習(xí)過程中再繼續(xù)深入介紹鳄乏。
字典 dictionary
字典顧名思義,就像字典一樣每個字對應(yīng)一段解釋文字棘利,Python中的字典也是非常典型的以逗號分隔以冒號分割的鍵值對數(shù)據(jù)結(jié)構(gòu)橱野,形式為:{鍵1:值1, 鍵2:值2, 鍵3:值3,}
。和列表類似善玫,字典的鍵和值對于賦值對象是比較包容的水援。而且注意到我們在字典的最后一個鍵值對后面還是加上了逗號。
這個逗號不是必須的茅郎,列表中我們不一定會這么做蜗元,但是在字典中我推薦這樣做。原因和字典的嵌套有關(guān)系冗。
sample_dict = {key1:value1,
key2:{value2_key1:value2_value1,
value2_key2:value2_value2,
value2_key3:{value2_value3_key1:value2_value3_value1,
value2_value3_key2:value2_value3_value2,
value2_value3_key3:value2_value3_value3,
value2_value3_key4:value2_value3_value4,
},
value2_key4:value2_value4,
},
key3:value3,
key4:value4,
}
不要被上面的這個字典嚇到奕扣。事實上我們真正遇到的字典往往就是比這個還要復(fù)雜,但嵌套起來其實也很直觀掌敬,一目了然惯豆。我們以后處理json數(shù)據(jù)的話就會將其轉(zhuǎn)換為Python字典進(jìn)行操作。注意到由于每個元素后面都有逗號涝开,我們可以輕松地在某個元素后面回車來進(jìn)行插入新的鍵值對的操作循帐。也可以簡單地理解為框仔,這樣的話字典的每個元素就變成了“鍵:值舀武,”,從第一個元素到最后一個元素都是這樣离斩,而我們在尾端按這種格式添加字段的時候银舱,這種結(jié)構(gòu)不會被破壞瘪匿。
當(dāng)然了,就和逗號后面加空格一樣寻馏,這種事情做不做一般不會影響程序的執(zhí)行棋弥,但是我們應(yīng)該養(yǎng)成好習(xí)慣,提高代碼可讀性诚欠,減少出錯的機(jī)會顽染。或者如果使用的字典很簡短轰绵,那么也可以不打最后這個逗號粉寞。
字典的鍵值對結(jié)構(gòu)給其帶來了列表無法比擬的強(qiáng)大之處。它能完成信息檢索的功能左腔,這是非常實用的功能唧垦,它可以輕松的建立映射結(jié)構(gòu)。字典可以輕松地勝任圖數(shù)據(jù)結(jié)構(gòu)液样,這對列表來說非常吃力振亮。字典可以完成表驅(qū)動的一系列操作,使程序簡潔優(yōu)雅鞭莽,思路清晰坊秸。另外,Python的字典是基于散列實現(xiàn)澎怒,這使得它的檢索速度非掣窘铮快。字典相關(guān)的深入的數(shù)據(jù)結(jié)構(gòu)知識都是一些高級數(shù)據(jù)結(jié)構(gòu)相關(guān)內(nèi)容丹拯,這些知識都非常有趣站超,我會認(rèn)真考慮是否有必要開數(shù)據(jù)結(jié)構(gòu)專題的。
字典的索引
字典有類似于列表的索引訪問方式乖酬。字典[鍵]
這種形式就可以訪問該鍵對應(yīng)的值死相。
>>> a = {'b':'c', 'd':'e',}
>>> a['b']
'c'
>>> a.get('b')
'c'
我們這里順便介紹一下get
方法,我們看到它和索引類似返回了字典鍵對應(yīng)的值咬像,那么它和索引有什么區(qū)別呢算撮。
區(qū)別在于,我們?nèi)绻靡粋€不存在的鍵去索引就會和引發(fā)和列表IndexError索引錯誤類似的KeyError鍵錯誤县昂。但是get方法不會肮柜,如果在字典中找不到對應(yīng)的鍵,則會返回一個None
倒彰。那是不是get方法就一定比索引好用呢审洞,也不是的。
因為字典的索引還可以用來賦值待讳,修改字典或者添加鍵值對芒澜。當(dāng)索引的鍵是字典中已經(jīng)存在時仰剿,賦值語句會修改該鍵對應(yīng)的值,如果字典中并不存在這樣的鍵痴晦,則會新建一個鍵值對加入字典中南吮。
字典遍歷
字典的遍歷主要分兩種,鍵和值的遍歷誊酌。首先字典本身是可迭代的部凑,迭代的對象是字典中的鍵。
>>> a = {'b':'c', 'd':'e',}
>>> for i in a:
... print(type(i), i)
...
<class 'str'> b
<class 'str'> d
這樣做有一種好處碧浊,我們得到了鍵砚尽,也就能用索引訪問它對應(yīng)的值,這樣也就間接遍歷了字典中的值辉词。說到底必孤,字典的迭代的基本單位應(yīng)該是鍵值對,但是Python把鍵作為鍵值對的代表瑞躺,增加的操作的靈活性敷搪。
>>> a = {'b':'c', 'd':'e',}
>>> for i in a:
... print(a[i])
...
c
e
除了直接遍歷,我們也可以使用字典的內(nèi)置方法來直接得到我們需要的結(jié)果
>>> a = {'b':'c', 'd':'e',}
>>> a.keys()
dict_keys(['b', 'd'])
>>> a.values()
dict_values(['c', 'e'])
>>> a,items()
dict_items([('b', 'c'), ('d', 'e')])
需要說明的是這三個內(nèi)置函數(shù)返回對象雖然不是列表幢哨,但都是可迭代對象赡勘,這意味著我們我們也可以用這些來遍歷字典。
>>> a = {'b':'c', 'd':'e',}
>>> for i in a.items():
... print(type(i), i)
...
<class 'tuple'> ('b', 'c')
<class 'tuple'> ('d', 'e')
注意到items()
方法的迭代對象是一個(鍵, 值)
兩個元素組成的元組捞镰。元祖是我們接下來馬上要介紹的一種數(shù)據(jù)結(jié)構(gòu)闸与。而且其實我們之前用的直接對字典遍歷for i in a
其實就相當(dāng)于for i in a.keys()
字典的一些內(nèi)置方法
其實在上面我們已經(jīng)接觸了一些字典的內(nèi)置方法,他們都是一些訪問字典內(nèi)容的方法岸售,這里我們要講的主要是pop
和popitem
這兩個從字典中彈出元素的方法践樱。
我們在介紹列表時已經(jīng)接觸過pop
方法了,但是字典的pop
方法不一樣凸丸,字典的pop
方法需要指定一個鍵作為參數(shù)拷邢,返回值為該鍵在字典中對應(yīng)的值。
>>> a = {'b':'c', 'd':'e',}
>>> a.pop('b')
'c'
>>> a
{'d': 'e'}
除了用pop
方法外屎慢,也可以用諸如del a['b']
這樣的形式用內(nèi)置方法從字典中刪除指定鍵值對瞭稼,但是不同的是不會有返回值。事實上支持索引的可變數(shù)據(jù)類型基本上都支持del
方法腻惠。
那么這個popitem
方法又是用來干什么的呢环肘。其實字典的popitem
方法倒更像列表的pop
方法,注意只是“像”集灌,這兩者還是有區(qū)別的悔雹。
>>> a = {'b':'c', 'd':'e',}
>>> a.popitem()
('d', 'e')
>>> a
{'b': 'c'}
注意到字典popitem
方法和列表pop
方法一樣不需要參數(shù),會彈出一個元素作為返回值。這個返回值的形式和items
方法的迭代對象類似荠商,是個兩個元素的元組寂恬。但這不是我要說的區(qū)別续誉,我要說的區(qū)別是莱没,Python列表是順序表,所以它能彈出最后一個元素酷鸦,但是Python字典的鍵值對的順序其實是不可靠的饰躲,不要寄希望于popitem
方法來彈出最后插入的鍵值對,可以認(rèn)為popitem
方法是從字典中隨機(jī)彈出一個鍵值對臼隔。
要想按插入順序后入先出彈出字典元素可以使用collections
中的OrderedDict
嘹裂,文末會與其他標(biāo)準(zhǔn)庫補(bǔ)充數(shù)據(jù)類型一起進(jìn)行更多討論。
由于有了介紹時列表建立的一部分基礎(chǔ)摔握,字典理解起來和列表進(jìn)行對比記憶就行了寄狼。還是那句話,更深入的應(yīng)用會在可能出現(xiàn)的數(shù)據(jù)結(jié)構(gòu)部分講解氨淌,本文目前更需要關(guān)注的是理解這些數(shù)據(jù)結(jié)構(gòu)的特點與簡單的使用方法泊愧,和在實際操作中去熟悉這些數(shù)據(jù)結(jié)構(gòu)。
元組 tuple 和 集合 set
元組tuple
和集合set
是Python中的另外兩個常用數(shù)據(jù)結(jié)構(gòu)盛正。我們在上文中強(qiáng)調(diào)過列表list是可變對象删咱,可以把元組tuple簡單理解為不可變的列表。這意味著列表支持的許多操作元組是不支持的豪筝,但這同時也使得元組比列表安全許多痰滋。因此,在Python許多內(nèi)置操作和函數(shù)中续崖,元組的應(yīng)用反而要比列表更加廣泛敲街。
我們說可以把元組tuple簡單理解為不可變的列表,類似的严望,集合set
可以簡單理解為不含重復(fù)元素的列表聪富。
創(chuàng)建元組
元組創(chuàng)建方式和列表類似,但是符號改成圓括號著蟹。
>>> a = ('b', 'c', 'd')
>>>type(a)
<class 'tuple'>
>>> b = 'c', 'd', 'e'
>>> type(b)
<class 'tuple'>
>>> print(b)
('c', 'd', 'e')
注意到我們還使用了一種直接定義方式墩蔓, 當(dāng)我們在賦值語句的等號右邊用逗號分隔多個對象時,如果等號左邊只有一個變量萧豆,那么這些對象將會作為元組的元素賦值給該變量奸披。和字典一樣,元組的逗號也有一點規(guī)定涮雷,單個元素的元組阵面,元素后面必須加上逗號。
在這里我們要把定義列表的方法再拿出來說一說,我們是不是可以認(rèn)為列表的一般賦值語句sample_list = [elem1, elem2]
是相當(dāng)于對元組對象(elem1, elem2)
用[]
的形式使用了Python內(nèi)置的list函數(shù)將其轉(zhuǎn)換成列表呢样刷?
是也不是仑扑。Python的列表生成操作中[]
或者list(iter)
其實是用于任何可迭代對象的函數(shù)。而我們見到的b = 'c', 'd', 'e'
賦值直接把其作為元組保存是因為置鼻,Python中元組比列表是更優(yōu)先的元素存儲數(shù)據(jù)結(jié)構(gòu)镇饮。這也是我說Python中元組可能比列表反而應(yīng)用更廣泛的原因,元組是Python中默認(rèn)的最基本可迭代對象形式箕母。關(guān)于賦值語句右端用逗號分隔的對象是可迭代對象储藐,我們會在下一篇函數(shù)參數(shù)相關(guān)知識中進(jìn)行講解。
而元組比列表更被Python推崇的原因恰好就在于嘶是,元組是不可變的對象钙勃。這使它被認(rèn)為是更“安全”的。這也是我們在上文中花大量篇幅說列表是可變對象的原因之一聂喇,元組與列表最大的區(qū)別就在此辖源。
元組的基本操作
元組可以像列表一樣索引和切片,可以用加法和乘法希太,可以用len獲取長度克饶。但是列表那些改變自己本身的操作元組是不支持的,比如說pop和append或者像sort和reverse方法跛十。即使元組也是有序的彤路,但它不能做這些事情。
但是元組的元素排序不是做不到的芥映,我們用sorted(iter)
這個內(nèi)置函數(shù)就可以了洲尊。這個函數(shù)就像list(iter)
函數(shù)一樣可以對任何可迭代對象使用,但是返回結(jié)果是列表奈偏。我們可以對返回結(jié)果使用tuple()
函數(shù)再賦值給某個變量就可以了坞嘀。當(dāng)然,你也看出來了惊来,這有點蛋疼丽涩,還是直接用列表吧。
那么什么時候該用列表裁蚁,什么時候該用元組呢矢渊。我的建議是,當(dāng)元素類型單一結(jié)構(gòu)相似而變化范圍較大的時候使用列表枉证,這樣可以方便我們進(jìn)行操作矮男。而元素類型復(fù)雜或者元素類型單一但是元素相對確定的時候,我們可以使用元組作為變量保存池室谚。舉例來說星期一到星期天這七天更適合存儲在元組中毡鉴,而我們的待辦事項更適合存儲在列表中崔泵。當(dāng)然,如果要將星期和待辦事項進(jìn)行對應(yīng)猪瞬,我們應(yīng)該使用字典憎瘸。
說到字典。我們上文中提到字典對存儲對象比較寬容陈瘦,而討論列表時我們用的卻是非常寬容』细剩現(xiàn)在我們看看為什么字典不那么寬容。我們提到過Python字典某種程度上其實可以看作是哈希表甘晤,這要求Python字典的鍵是常量含潘,也就是我強(qiáng)調(diào)過的不可變對象饲做,因此线婚,元組可以作為字典中的鍵,但是列表不可以盆均。
在下一篇函數(shù)中塞弊,我們還會遇到元組和列表對比相關(guān)的問題。
集合
我并不打算對集合展開太多的內(nèi)容泪姨。就像之前所提到過的游沿,我們只要將其理解成不包含重復(fù)元素的列表即可。不過需要注意的是集合中的元素是無序的肮砾,因此集合不支持索引诀黍。在這里只簡單介紹一下集合的定義方式。
>>> a = {'b', 'c', 'd', 'd'}
>>> print(type(a), a)
<class 'set'> {'c', 'd', 'b'}
>>> a = {}
>>> type(a)
<class 'dict'>
注意到集合在定義時會自動刪除重復(fù)的元素仗处。集合的符號和字典類似眯勾,但是我們定義空集合時不能使用{}
,這樣得到的是一個空字典婆誓。我們可以用定義集合的另一種方式定義空集合吃环,Python中對可迭代對象可以像列表和元組一樣使用set()
函數(shù)來生成一個新集合。
>>> a = set()
>>> print(type(a), a)
<class 'set'> set()
>>> a.add('b', 'c')
>>> a
{'b', 'c'}
>>> a.add('c', 'd')
>>> a
{'b', 'c', 'd'}
注意到我們使用add方法向集合中添加了元素洋幻,這說明集合也是可變對象郁轻。而且不管我們使用什么樣的方法,集合中永遠(yuǎn)不會出現(xiàn)重復(fù)的元素文留。
另外值得一提的是集合之間的操作好唯,即數(shù)學(xué)意義上的交、并燥翅、補(bǔ)骑篙、異或,它們對應(yīng)的操作符分別是&
, |
, -
, ^
权旷。
Python其他內(nèi)置數(shù)據(jù)結(jié)構(gòu)
在Python的標(biāo)準(zhǔn)庫collections
中還有許多適應(yīng)不同需求的其他數(shù)據(jù)結(jié)構(gòu)替蛉,常見的比如deque
用來比列表更好地完成堆棧和隊列的功能贯溅,Counter
用來以字典的形式保存列表中元素出現(xiàn)的次數(shù),OrderDict
用來構(gòu)造有序的字典躲查,defaultdict
用來構(gòu)造有默認(rèn)值的字典它浅,以及其他一系列的用來構(gòu)造自定義復(fù)雜數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)數(shù)據(jù)類型類。
這些數(shù)據(jù)結(jié)構(gòu)應(yīng)用比較靈活镣煮,在以后的實際操作中偶爾會接觸到他們姐霍。在這里我也不打算展開來介紹了。
最后的廢話
講到這里我想要講的有關(guān)Python簡單數(shù)據(jù)結(jié)構(gòu)內(nèi)容就基本結(jié)束了典唇。我們把主要精力放在了列表上镊折。我們通過列表熟悉了數(shù)據(jù)結(jié)構(gòu)是用來做什么的,大概會具有什么樣的功能介衔,因為列表是功能比較齊全恨胚,比較典型而且應(yīng)用廣泛的數(shù)據(jù)結(jié)構(gòu)。這樣我們理解元組和集合比較簡單炎咖,一個可以簡單理解為不可變列表赃泡,一個可以理解為不含重復(fù)元素的無序列表。另一個我們介紹比較詳細(xì)的是字典乘盼,這種數(shù)據(jù)格式是互聯(lián)網(wǎng)信息交互中應(yīng)用最廣泛的類似數(shù)據(jù)形式升熊。
最后再說明一下為什么我會猶豫要不要詳細(xì)講Python中的復(fù)雜數(shù)據(jù)結(jié)構(gòu),開個專門的板塊來介紹绸栅。畢竟在我計劃中字符串都是有單獨板塊的级野。
這是因為我認(rèn)為Python中數(shù)據(jù)結(jié)構(gòu)沒那么重要。我覺得Python的學(xué)習(xí)非常簡單粹胯,一個是面向?qū)ο蟮睦砟钜鋵嵄腿幔粋€是放開思路靈活運(yùn)用第三方庫。前一個幫助我們理解別人的代碼到底是什么意思矛双,后者能夠幫助我們快速地解決問題渊抽。一切編程方法,無論是面向這個面向那個议忽,基于這個基于那個懒闷,說到底是基于計算機(jī)面向問題編程。能解決問題才是關(guān)鍵栈幸。
因此愤估,如果有數(shù)據(jù)結(jié)構(gòu)基礎(chǔ),那么完全沒有必要學(xué)習(xí)如何用Python構(gòu)造樹和圖或者哈希表速址。簡單的就簡單用deque
完成棧隊功能或者使用字典即可玩焰,復(fù)雜的可能說你思路需要修改或者說根本不適合用Python來完成復(fù)雜數(shù)據(jù)結(jié)構(gòu)。
這樣說可能顯得我根本不需要猶豫芍锚,直接略過數(shù)據(jù)結(jié)構(gòu)就可以了昔园。但是不是這樣的蔓榄。我覺得任何想要深入編程技術(shù)的人,都應(yīng)該學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)默刚,應(yīng)該看一下優(yōu)秀的前輩是如何解決問題的甥郑。為什么他們可以想出這樣的數(shù)據(jù)結(jié)構(gòu)完成這樣的功能。再一點荤西,用Python來學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)澜搅,剛好就是熟悉Python面向?qū)ο蟮淖詈梅绞健N覀円部梢酝ㄟ^學(xué)習(xí)比較看出Python面向?qū)ο髸r與其他語言的異同點邪锌。
在這里先在文末給大家推薦兩個通過學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)的途徑勉躺。一個是在線的英文網(wǎng)站,文章內(nèi)容很高觅丰,而且支持實時操作測試饵溅,如果沒有語言障礙會是非常好的學(xué)習(xí)體驗,強(qiáng)烈推薦舶胀。另一個是我看過的一本實體書概说,北大老師寫的《數(shù)據(jù)結(jié)構(gòu)與算法:Python語言描述》碧注。我直言不諱地講里面的代碼質(zhì)量不高嚣伐,很多地方值得改進(jìn),對Python特點理解得也不算透徹萍丐,很多代碼可以明顯看出C的痕跡轩端。但是這本書相對簡單,很扎實绰播,講解也比較詳盡逛绵,對于初學(xué)者來說也算不錯的學(xué)習(xí)材料漓拾。如果要學(xué)習(xí)系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)與算法的知識,coursera上有大量的斯坦福和普林斯頓等國外高校的優(yōu)質(zhì)課程拱层,這里就不貼鏈接了,會科學(xué)上網(wǎng)不至于不知道coursera宴咧。