Python進(jìn)階-簡單數(shù)據(jù)結(jié)構(gòu)

本文為《爬著學(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)容的方法岸售,這里我們要講的主要是poppopitem這兩個從字典中彈出元素的方法践樱。

我們在介紹列表時已經(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宴咧。

鏈接

  1. Problem Solving with Algorithms and Data Structures using Python — Problem Solving with Algorithms and Data Structures
  2. 數(shù)據(jù)結(jié)構(gòu)與算法:Python語言描述 (豆瓣)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末根灯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子掺栅,更是在濱河造成了極大的恐慌烙肺,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氧卧,死亡現(xiàn)場離奇詭異桃笙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)沙绝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門搏明,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鼠锈,“玉大人,你說我怎么就攤上這事星著〗潘睿” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵强饮,是天一觀的道長由桌。 經(jīng)常有香客問我,道長邮丰,這世上最難降的妖魔是什么行您? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮剪廉,結(jié)果婚禮上娃循,老公的妹妹穿的比我還像新娘。我一直安慰自己斗蒋,他們只是感情好捌斧,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泉沾,像睡著了一般捞蚂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跷究,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天姓迅,我揣著相機(jī)與錄音,去河邊找鬼俊马。 笑死丁存,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的柴我。 我是一名探鬼主播解寝,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼艘儒!你這毒婦竟也來了聋伦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤彤悔,失蹤者是張志新(化名)和其女友劉穎嘉抓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晕窑,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡抑片,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了杨赤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敞斋。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡截汪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出植捎,到底是詐尸還是另有隱情衙解,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布焰枢,位于F島的核電站蚓峦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏济锄。R本人自食惡果不足惜暑椰,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荐绝。 院中可真熱鬧一汽,春花似錦、人聲如沸低滩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恕沫。三九已至监憎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昏兆,已是汗流浹背枫虏。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留爬虱,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓腾它,卻偏偏與公主長得像跑筝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瞒滴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容