本文為《爬著學Python》系列第九篇文章。
從現(xiàn)在開始算是要進入“真刀真槍”的Python學習了。之所以這么說,是因為學完Python進階模塊以后,基本上就具備了一定的Python編程能力了??梢哉f我們可以用不那么聰明的方法實現(xiàn)我們要實現(xiàn)的一切功能,而更高級的技巧則會在Python精進中介紹。
本文的目的在于介紹Python的內置簡單數(shù)據(jù)結構,重點是列表list和字典dict,也會簡單介紹元祖tuple和集合set。我所說的“簡單”,不是指這些數(shù)據(jù)結構學習起來難度低、或者說功能比較單一(其實相反),而是說這些數(shù)據(jù)結構在Python中最常用,形式最簡單,而我們要介紹的又是他們的一些簡單用法。
列表 list
任何了解過數(shù)據(jù)結構的人都應該明白,列表永遠是應用最廣泛,功能最多樣化的數(shù)據(jù)結構,這樣的數(shù)據(jù)結構是不應該稱之為“簡單”的。Python中的列表尤是如此。Python中的列表是一種分離式動態(tài)順序表,可以實現(xiàn)非常多的數(shù)據(jù)結構,如可以直接作為棧,可以實現(xiàn)隊列,可以直接表示完全二叉樹所以可以作為堆使用,可以嵌套表示一般樹形結構,用自定義類進行簡單改造能更加高效地實現(xiàn)以上的以及其他許多功能……
在介紹完列表的基礎知識以后,會對上面這些結構的實現(xiàn)進行簡單示例。
創(chuàng)建列表
Python中對于列表的使用就像普通變量一樣,不需要變量聲明,直接將列表元素用方括號[]
框起來進行賦值即可。例如
>>> sample1 = [1, '2', 'woo!']
需要注意的是Python的列表可以同時接受不同類型的元素,非常寬容,無論是數(shù)字、布爾值還是字符串、其他變量、其他數(shù)據(jù)結構,甚至是函數(shù),只要是對象,都可以作為列表元素保存在列表中。這也給Python列表操作帶來了各種可能性,應用場景中充分發(fā)揮想象力可以使代碼非常優(yōu)雅簡潔。
另外,我們可以對一切可迭代對象使用內置函數(shù)list()
來獲得一個新的列表(在下一篇自定義函數(shù)文中,我們會仔細討論為什么說這是個新列表)。
>>> sample2 = list(sample1)
對于一般可迭代對象,我們還可以用列表生成器來完成生成列表的工作(關于迭代器和生成器會在更靠后的文章中進行介紹)。
>>> sample3 = [i for i in range(10) if i%2==0]
至此我們介紹了三種新建列表的方法:
- 一般賦值語句
- list() 函數(shù)轉換可迭代對象
- 列表生成器
在文末介紹元祖以后,我們會認識到其實這些方法都是殊途同歸的。
列表的一些基本操作
我們提到過Python的內置列表是一種分離式動態(tài)順序表,我們可以先不用知道這是什么意思,只要知道這代表Python列表是可索引的而且可以幾乎無限制地往里面插入數(shù)據(jù)(如果以后確定開數(shù)據(jù)結構模塊內容,會介紹實現(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中代表這個列表本身。為了消除歧義也為了計算機處理方便,從0
開始的索引就應運而生了,這其實也算是非常巧妙的設計,是工程師智慧的結晶。
在Python中雖然已經(jīng)沒有這樣做的必要了,但還是遵循了這一傳統(tǒng),這也是為了方便早期的程序員更快地適應這種新語言。因此我們在以后也可能會看到有些Python面向對象程序設計會把列表的第一個元素設置為a[0]=0
,然后用一個變量來保存真正在列表后面添加的元素個數(shù),可以先定義currentsize = 0
每次插入數(shù)據(jù)就currentsize += 1
,或者每次需要訪問長度時currentsize = len(a)-1
。這是為了讓列表更加符合邏輯而且在某些條件判斷處理上有許多優(yōu)勢,否則也可以用a[0]
來保存列表的真實長度。GitHub上有一部分Python程序員會采用這種方法,初次接觸不要奇怪人家為什么要這么做。
好吧,說了這么多只是為了加深印象,列表元素的索引是從0
開始的。另外一點要說的是,Python列表接受負數(shù)作為參數(shù)從列表尾端訪問元素,不一樣的是,列表尾端第一個元素的索引是-1
,后面以此類推。
>>> sample1 = [1, '2', 'woo!']
>>> elem1 = sample1[-2]
>>> elem1
'2'
接下來講列表元素的切片。在知道了索引的基礎上,切片非常好理解。索引是訪問列表中的一個元素,而切片則是訪問列表中的一段元素。
>>> 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ù),代表取到這一端的列表盡頭。
- 切片時也可以用負數(shù)當參數(shù),規(guī)則和索引類似。
- 切片可以和range函數(shù)一樣接受第三個參數(shù)作為步長,這個步長也可以是負數(shù),
[::-1]
這樣的索引方式可以方便地將列表倒轉。
在以上索引和切片的操作中還有一點要提的就是,如果索引的標簽超出列表范圍,程序就會拋出IndexError索引錯誤。
列表的加法和乘法
Python中的列表可以使用加法和乘法。那么為什么不能用減法和除法呢?因為索引和切片可以看作是使列表縮短的操作,這完成了減法和除法應該完成的任務。
Python列表的加法必須是兩個列表進行相加,效果是將兩個列表連接起來。
>>> 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ù),效果是將該列表重復。
>>> sample1 = [1, '2', 'woo!']
>>> sample1 * 2
[1, '2', 'woo!', 1, '2', 'woo!']
這兩個操作比較簡單,不多贅述了。自此我們掌握了一般的使列表長度變化的方法。接下來我們需要認識的是Python列表的一些簡單的其他內置方法。
列表的一些內置方法
這里涉及的都是一些簡單內置方法,包括insert,len,append,pop,reverse,sort, clear。
insert和len
insert
方法能夠將新元素插入到列表的指定位置,接受兩個參數(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ù)給定插入索引位置一樣可以是負數(shù),但是使用得不多。由于會把該位置原來的元素往后擠,所以即使是用-1當作參數(shù),新元素在操作結束后是列表的倒數(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方法進行區(qū)分所以就不作演示了。
append和pop
append
方法可以在列表尾端插入元素,彌補了insert函數(shù)的不足。而且遠遠不只是這樣,append操作的速度要比insert快上非常多。原因也是顯而易見的,append操作不會擠到列表中的元素所以不用更改其他元素的索引。對應的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列表天然地可以直接作為棧使用。棧是一種后進先出的緩存結構,在計算機中應用非常普遍。諸如遞歸、函數(shù)嵌套等,都是通過棧實現(xiàn)的。如果以后開了數(shù)據(jù)結構板塊我們會詳細介紹棧的強大。
reverse,sort和clear
reverse
方法會將列表反轉。sort
方法可以對列表元素進行遞增的排序,加入?yún)?shù)reverse=True
則可以進行遞減的排序。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的處理辦法是容量不足時列表等量擴容,但是容量冗余時不會自動清除多余的容量。還是那句話,如果仔細討論Python數(shù)據(jù)結構的話,會對相關內容進行詳細說明。
列表是“可變”對象
介紹完列表的一些基本操作以后,我們要講一個比較重要的知識點。這是Python可變對象的一些特性,是面試時幾乎必然會涉及的內容。我們在此處先簡單介紹一下,在下一篇文章自定義函數(shù)相關內容中會更加仔細地介紹Python中可變對象的特性為什么會這么重要。
首先我們要明確的一點是,列表是可變對象。在上面我們介紹過的一系列操作,都是對列表對象本身進行操作。列表應該是我在本專題中第一次介紹可變對象的概念。我們沒接觸過可變對象沒關系,我們接觸過不可變對象。
>>> a = 3
>>> b = a
>>> a = a + 1
>>> print(b)
3
在上面的操作中我們對a賦值為3,再將a的值賦給b,再將a的值加1,這時我們輸出b的值,b還是3。這一切看上去是理所當然的。那我們試一下對列表進行這樣的操作呢?
>>> 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應該變成了[3, 1]
。試一下輸出a,確實是這樣的。但這時我們輸出列表b,b還會是[3]
嗎?事實上不是的,現(xiàn)在列表b也變成了[3, 1]
。
這就是這個知識點重要的原因,我們沒有對列表b進行操作,可是它卻改變了。如果在實際項目中經(jīng)常出現(xiàn)這樣的現(xiàn)象,那恐怕bug多得永遠也清不完。這要求我們要時刻清楚地意識到Python對象的特性。這里先簡單說一下會發(fā)生這種現(xiàn)象的原因,具體的細節(jié)以及正確的操作方法在下一篇文章會重點介紹。
簡單來說就是對于可變對象,它的許多方法會修改這個對象本身,而對于不可變對象則是重新創(chuàng)建一個新對象。我們將值為3的a加1,事實上是將a指向了一個值等于3+1的新對象。而我們在列表[3]
后面接上列表[1]
,它則是把列表[1]
中的元素依次放進列表[3]
的尾端。這兩種操作的區(qū)別是,前者創(chuàng)建了新對象而后者沒有,后者只是對原對象進行修改。因此如果a和b都是指向這個列表,那么這個列表變動時,a和b都會同時受到牽連。
這種特性要求我們分辨清楚,哪些操作是針對可變對象本身的,哪些操作是將可變對象作為參數(shù)參與運算而不進行修改的。如果我們不能合理地規(guī)避那些不應該出現(xiàn)的修改,那么將會有無盡的奇怪bug出現(xiàn)在我們的程序中。而分辨方法比較簡單,就和之前我們分辨函數(shù)變量和函數(shù)對象類似。如果列表對象出現(xiàn)在賦值語句的左邊,可以認為是對列表變量進行修改,如果在右邊則要分情況,如切片和索引就只訪問列表但不做修改,但是像resort方法pop方法就會修改列表了。
相信看到這里你大概能理解為什么我把標題取為“簡單數(shù)據(jù)結構”,單是列表,在絕大部分內容我們都是淺嘗輒止的情況下,已經(jīng)是非常大的篇幅了。我只能淺顯地介紹一下相關內容,更多內容在后面的學習過程中再繼續(xù)深入介紹。
字典 dictionary
字典顧名思義,就像字典一樣每個字對應一段解釋文字,Python中的字典也是非常典型的以逗號分隔以冒號分割的鍵值對數(shù)據(jù)結構,形式為:{鍵1:值1, 鍵2:值2, 鍵3:值3,}
。和列表類似,字典的鍵和值對于賦值對象是比較包容的。而且注意到我們在字典的最后一個鍵值對后面還是加上了逗號。
這個逗號不是必須的,列表中我們不一定會這么做,但是在字典中我推薦這樣做。原因和字典的嵌套有關。
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,
}
不要被上面的這個字典嚇到。事實上我們真正遇到的字典往往就是比這個還要復雜,但嵌套起來其實也很直觀,一目了然。我們以后處理json數(shù)據(jù)的話就會將其轉換為Python字典進行操作。注意到由于每個元素后面都有逗號,我們可以輕松地在某個元素后面回車來進行插入新的鍵值對的操作。也可以簡單地理解為,這樣的話字典的每個元素就變成了“鍵:值,”,從第一個元素到最后一個元素都是這樣,而我們在尾端按這種格式添加字段的時候,這種結構不會被破壞。
當然了,就和逗號后面加空格一樣,這種事情做不做一般不會影響程序的執(zhí)行,但是我們應該養(yǎng)成好習慣,提高代碼可讀性,減少出錯的機會。或者如果使用的字典很簡短,那么也可以不打最后這個逗號。
字典的鍵值對結構給其帶來了列表無法比擬的強大之處。它能完成信息檢索的功能,這是非常實用的功能,它可以輕松的建立映射結構。字典可以輕松地勝任圖數(shù)據(jù)結構,這對列表來說非常吃力。字典可以完成表驅動的一系列操作,使程序簡潔優(yōu)雅,思路清晰。另外,Python的字典是基于散列實現(xiàn),這使得它的檢索速度非常快。字典相關的深入的數(shù)據(jù)結構知識都是一些高級數(shù)據(jù)結構相關內容,這些知識都非常有趣,我會認真考慮是否有必要開數(shù)據(jù)結構專題的。
字典的索引
字典有類似于列表的索引訪問方式。字典[鍵]
這種形式就可以訪問該鍵對應的值。
>>> a = {'b':'c', 'd':'e',}
>>> a['b']
'c'
>>> a.get('b')
'c'
我們這里順便介紹一下get
方法,我們看到它和索引類似返回了字典鍵對應的值,那么它和索引有什么區(qū)別呢。
區(qū)別在于,我們如果用一個不存在的鍵去索引就會和引發(fā)和列表IndexError索引錯誤類似的KeyError鍵錯誤。但是get方法不會,如果在字典中找不到對應的鍵,則會返回一個None
。那是不是get方法就一定比索引好用呢,也不是的。
因為字典的索引還可以用來賦值,修改字典或者添加鍵值對。當索引的鍵是字典中已經(jīng)存在時,賦值語句會修改該鍵對應的值,如果字典中并不存在這樣的鍵,則會新建一個鍵值對加入字典中。
字典遍歷
字典的遍歷主要分兩種,鍵和值的遍歷。首先字典本身是可迭代的,迭代的對象是字典中的鍵。
>>> a = {'b':'c', 'd':'e',}
>>> for i in a:
... print(type(i), i)
...
<class 'str'> b
<class 'str'> d
這樣做有一種好處,我們得到了鍵,也就能用索引訪問它對應的值,這樣也就間接遍歷了字典中的值。說到底,字典的迭代的基本單位應該是鍵值對,但是Python把鍵作為鍵值對的代表,增加的操作的靈活性。
>>> a = {'b':'c', 'd':'e',}
>>> for i in a:
... print(a[i])
...
c
e
除了直接遍歷,我們也可以使用字典的內置方法來直接得到我們需要的結果
>>> 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')])
需要說明的是這三個內置函數(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ù)結構。而且其實我們之前用的直接對字典遍歷for i in a
其實就相當于for i in a.keys()
字典的一些內置方法
其實在上面我們已經(jīng)接觸了一些字典的內置方法,他們都是一些訪問字典內容的方法,這里我們要講的主要是pop
和popitem
這兩個從字典中彈出元素的方法。
我們在介紹列表時已經(jīng)接觸過pop
方法了,但是字典的pop
方法不一樣,字典的pop
方法需要指定一個鍵作為參數(shù),返回值為該鍵在字典中對應的值。
>>> a = {'b':'c', 'd':'e',}
>>> a.pop('b')
'c'
>>> a
{'d': 'e'}
除了用pop
方法外,也可以用諸如del a['b']
這樣的形式用內置方法從字典中刪除指定鍵值對,但是不同的是不會有返回值。事實上支持索引的可變數(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
方法來彈出最后插入的鍵值對,可以認為popitem
方法是從字典中隨機彈出一個鍵值對。
要想按插入順序后入先出彈出字典元素可以使用collections
中的OrderedDict
,文末會與其他標準庫補充數(shù)據(jù)類型一起進行更多討論。
由于有了介紹時列表建立的一部分基礎,字典理解起來和列表進行對比記憶就行了。還是那句話,更深入的應用會在可能出現(xiàn)的數(shù)據(jù)結構部分講解,本文目前更需要關注的是理解這些數(shù)據(jù)結構的特點與簡單的使用方法,和在實際操作中去熟悉這些數(shù)據(jù)結構。
元組 tuple 和 集合 set
元組tuple
和集合set
是Python中的另外兩個常用數(shù)據(jù)結構。我們在上文中強調過列表list是可變對象,可以把元組tuple簡單理解為不可變的列表。這意味著列表支持的許多操作元組是不支持的,但這同時也使得元組比列表安全許多。因此,在Python許多內置操作和函數(shù)中,元組的應用反而要比列表更加廣泛。
我們說可以把元組tuple簡單理解為不可變的列表,類似的,集合set
可以簡單理解為不含重復元素的列表。
創(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')
注意到我們還使用了一種直接定義方式, 當我們在賦值語句的等號右邊用逗號分隔多個對象時,如果等號左邊只有一個變量,那么這些對象將會作為元組的元素賦值給該變量。和字典一樣,元組的逗號也有一點規(guī)定,單個元素的元組,元素后面必須加上逗號。
在這里我們要把定義列表的方法再拿出來說一說,我們是不是可以認為列表的一般賦值語句sample_list = [elem1, elem2]
是相當于對元組對象(elem1, elem2)
用[]
的形式使用了Python內置的list函數(shù)將其轉換成列表呢?
是也不是。Python的列表生成操作中[]
或者list(iter)
其實是用于任何可迭代對象的函數(shù)。而我們見到的b = 'c', 'd', 'e'
賦值直接把其作為元組保存是因為,Python中元組比列表是更優(yōu)先的元素存儲數(shù)據(jù)結構。這也是我說Python中元組可能比列表反而應用更廣泛的原因,元組是Python中默認的最基本可迭代對象形式。關于賦值語句右端用逗號分隔的對象是可迭代對象,我們會在下一篇函數(shù)參數(shù)相關知識中進行講解。
而元組比列表更被Python推崇的原因恰好就在于,元組是不可變的對象。這使它被認為是更“安全”的。這也是我們在上文中花大量篇幅說列表是可變對象的原因之一,元組與列表最大的區(qū)別就在此。
元組的基本操作
元組可以像列表一樣索引和切片,可以用加法和乘法,可以用len獲取長度。但是列表那些改變自己本身的操作元組是不支持的,比如說pop和append或者像sort和reverse方法。即使元組也是有序的,但它不能做這些事情。
但是元組的元素排序不是做不到的,我們用sorted(iter)
這個內置函數(shù)就可以了。這個函數(shù)就像list(iter)
函數(shù)一樣可以對任何可迭代對象使用,但是返回結果是列表。我們可以對返回結果使用tuple()
函數(shù)再賦值給某個變量就可以了。當然,你也看出來了,這有點蛋疼,還是直接用列表吧。
那么什么時候該用列表,什么時候該用元組呢。我的建議是,當元素類型單一結構相似而變化范圍較大的時候使用列表,這樣可以方便我們進行操作。而元素類型復雜或者元素類型單一但是元素相對確定的時候,我們可以使用元組作為變量保存池。舉例來說星期一到星期天這七天更適合存儲在元組中,而我們的待辦事項更適合存儲在列表中。當然,如果要將星期和待辦事項進行對應,我們應該使用字典。
說到字典。我們上文中提到字典對存儲對象比較寬容,而討論列表時我們用的卻是非常寬容?,F(xiàn)在我們看看為什么字典不那么寬容。我們提到過Python字典某種程度上其實可以看作是哈希表,這要求Python字典的鍵是常量,也就是我強調過的不可變對象,因此,元組可以作為字典中的鍵,但是列表不可以。
在下一篇函數(shù)中,我們還會遇到元組和列表對比相關的問題。
集合
我并不打算對集合展開太多的內容。就像之前所提到過的,我們只要將其理解成不包含重復元素的列表即可。不過需要注意的是集合中的元素是無序的,因此集合不支持索引。在這里只簡單介紹一下集合的定義方式。
>>> a = {'b', 'c', 'd', 'd'}
>>> print(type(a), a)
<class 'set'> {'c', 'd', 'b'}
>>> a = {}
>>> type(a)
<class 'dict'>
注意到集合在定義時會自動刪除重復的元素。集合的符號和字典類似,但是我們定義空集合時不能使用{}
,這樣得到的是一個空字典。我們可以用定義集合的另一種方式定義空集合,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方法向集合中添加了元素,這說明集合也是可變對象。而且不管我們使用什么樣的方法,集合中永遠不會出現(xiàn)重復的元素。
另外值得一提的是集合之間的操作,即數(shù)學意義上的交、并、補、異或,它們對應的操作符分別是&
, |
, -
, ^
。
Python其他內置數(shù)據(jù)結構
在Python的標準庫collections
中還有許多適應不同需求的其他數(shù)據(jù)結構,常見的比如deque
用來比列表更好地完成堆棧和隊列的功能,Counter
用來以字典的形式保存列表中元素出現(xiàn)的次數(shù),OrderDict
用來構造有序的字典,defaultdict
用來構造有默認值的字典,以及其他一系列的用來構造自定義復雜數(shù)據(jù)結構的基礎數(shù)據(jù)類型類。
這些數(shù)據(jù)結構應用比較靈活,在以后的實際操作中偶爾會接觸到他們。在這里我也不打算展開來介紹了。
最后的廢話
講到這里我想要講的有關Python簡單數(shù)據(jù)結構內容就基本結束了。我們把主要精力放在了列表上。我們通過列表熟悉了數(shù)據(jù)結構是用來做什么的,大概會具有什么樣的功能,因為列表是功能比較齊全,比較典型而且應用廣泛的數(shù)據(jù)結構。這樣我們理解元組和集合比較簡單,一個可以簡單理解為不可變列表,一個可以理解為不含重復元素的無序列表。另一個我們介紹比較詳細的是字典,這種數(shù)據(jù)格式是互聯(lián)網(wǎng)信息交互中應用最廣泛的類似數(shù)據(jù)形式。
最后再說明一下為什么我會猶豫要不要詳細講Python中的復雜數(shù)據(jù)結構,開個專門的板塊來介紹。畢竟在我計劃中字符串都是有單獨板塊的。
這是因為我認為Python中數(shù)據(jù)結構沒那么重要。我覺得Python的學習非常簡單,一個是面向對象的理念要落實,一個是放開思路靈活運用第三方庫。前一個幫助我們理解別人的代碼到底是什么意思,后者能夠幫助我們快速地解決問題。一切編程方法,無論是面向這個面向那個,基于這個基于那個,說到底是基于計算機面向問題編程。能解決問題才是關鍵。
因此,如果有數(shù)據(jù)結構基礎,那么完全沒有必要學習如何用Python構造樹和圖或者哈希表。簡單的就簡單用deque
完成棧隊功能或者使用字典即可,復雜的可能說你思路需要修改或者說根本不適合用Python來完成復雜數(shù)據(jù)結構。
這樣說可能顯得我根本不需要猶豫,直接略過數(shù)據(jù)結構就可以了。但是不是這樣的。我覺得任何想要深入編程技術的人,都應該學習數(shù)據(jù)結構,應該看一下優(yōu)秀的前輩是如何解決問題的。為什么他們可以想出這樣的數(shù)據(jù)結構完成這樣的功能。再一點,用Python來學習數(shù)據(jù)結構,剛好就是熟悉Python面向對象的最好方式。我們也可以通過學習比較看出Python面向對象時與其他語言的異同點。
在這里先在文末給大家推薦兩個通過學習數(shù)據(jù)結構的途徑。一個是在線的英文網(wǎng)站,文章內容很高,而且支持實時操作測試,如果沒有語言障礙會是非常好的學習體驗,強烈推薦。另一個是我看過的一本實體書,北大老師寫的《數(shù)據(jù)結構與算法:Python語言描述》。我直言不諱地講里面的代碼質量不高,很多地方值得改進,對Python特點理解得也不算透徹,很多代碼可以明顯看出C的痕跡。但是這本書相對簡單,很扎實,講解也比較詳盡,對于初學者來說也算不錯的學習材料。如果要學習系統(tǒng)的數(shù)據(jù)結構與算法的知識,coursera上有大量的斯坦福和普林斯頓等國外高校的優(yōu)質課程,這里就不貼鏈接了,會科學上網(wǎng)不至于不知道coursera。