簡介
數據結構基本上就是 – 可以將一些數據結合到一起的結構,換言之用於存儲一組相關的數據。
python擁有4種內建數據結構 – 列表,元組(tuple),字典和集合。
我們將看到如何它們,它們又是怎樣使我們的編程生涯變的愜意~
列表
列表是一種用於保存有序元素集合的數據結構,即你可以在列表中存儲元素序列。
考慮一個購物清單,上面有你需要購買的物品列表,只不過你可能希望以行分隔它們而到了python變成了逗號。
這樣想來就容易理解列表了吧。
列表元素應該被封閉在方括號中,這樣python才會明白你指定的是一個列表。
一但列表創建完畢,你可以對其元素進行添加,刪除和搜索。
正因為可以執行添加和刪除操作,我們將列表稱作可變類型,即這種類型可以被修改。
對象和類快速簡介
盡管我一直推遲討論對象和類,但現在需要對其進行少量的說明好讓你更好的理解列表。后面會在相應的章節深入研究類和對象。
列表是使用對象和類的一個例子。當我們為變量i賦值時,例如賦值5,這相當於創建一個int類(類型)的對象(實例)i。
事實上你可以閱讀help(int)的輸出更好的理解它。
一個類同樣可以擁有方法,即函數,而且它們只能應用於這個類。並且只有當你擁有一個類的對象時才能使用這些功能。
例如,python為列表類提供了一個append方法允許你將新的元素添加到列表尾。
舉個例子,mylist.append(‘an item’)將字符串添加到列表mylist的尾部。注意要使用點號訪問對象的方法。
一個類還可以擁有字段,而字段只不過是專門應用於一個類的變量而已。當你擁有對應類的對象時就能使用這些變量/名字了。
字段同樣利用點號訪問,例如mylist.field。
范例:
#!/usr/bin/python
# Filename: using_list.py
# This is my shopping list
shoplist = ['apple', 'mango', 'carrot', 'banana']
print('I have', len(shoplist), 'items to purchase.')
print('These items are:', end=' ')
for item in shoplist:
print(item, end=' ')
print('/nI also have to buy rice.')
shoplist.append('rice')
print('My shopping list is now', shoplist)
print('I will sort my list now')
shoplist.sort()
print('Sorted shopping list is', shoplist)
print('The first item I will buy is', shoplist[0])
olditem = shoplist[0]
del shoplist[0]
print('I bought the', olditem)
print('My shopping list is now', shoplist)
輸出:
$ python using_list.py
I have 4 items to purchase.
These items are: apple mango carrot banana
I also have to buy rice.
My shopping list is now ['apple', 'mango', 'carrot', 'banana', 'rice']
I will sort my list now
Sorted shopping list is ['apple', 'banana', 'carrot', 'mango', 'rice']
The first item I will buy is apple
I bought the apple
My shopping list is now ['banana', 'carrot', 'mango', 'rice']
工作流程:
變量shoplist是某個購物人的購物清單。
我們只在shoplist中存儲被購買物品的名字的字符串,但你也可以為列表增加任何其它種類的對象,包括數字甚至是其它列表。
我們通過for…in迭代列表元素,現在你一定意識到一個列表也是一個序列了吧。有關序列的特點我們會在后節討論。
注意print的end關鍵字實參,它指定我們希望以空格結束輸出而不是通常的換行。
接下來我們使用列表對象的append方法為列表添加一個新的元素。
為了確定元素真的被添加進去了,我們簡單的將列表傳給print函數,print函數整潔的將列表內容打印出來。
隨后我們使用列表的sort方法對列表進行排序,緊記sort會影響列表本身而不是返回一個被修改后的列表。
這與字符串的工作方式不同。這也是為什么說類標是可變類型而字符串是不可變類型的原因。
然后當在市場購買一樣東西后,我們希望將其從列表中刪除,del語句正是用武之地。
在這里我們指出希望刪除列表中的哪個元素,del就將這個元素從列表中刪除。
我們指定的是希望刪除列表的第一個元素,因此我們使用del shoplist[0](回想一下,python的索引從0開始)。
如果你想知道list對象的所有方法,詳見help(list)。
元組
元組用於保存各種各樣的對象。它與列表很相似,但它缺少列表提供的大量功能。
列表的一個主要特點就象字符串一樣,它是不可變類型,也就是說你不可以修改元組。
元組通過一組以逗號分隔的元素定義,並以一個可選的小括號閉合。
元組通常用於這樣的情形,一個語句或一個用戶定義的函數能夠安全的假設其使用的一組值(即元組值)不會發生改變。
范例:
#!/usr/bin/python
# Filename: using_tuple.py
zoo = ('python', 'elephant', 'penguin') # 注意小括號是可選的
print('Number of animals in the zoo is', len(zoo))
new_zoo = ('monkey', 'camel', zoo)
print('Number of cages in the new zoo is', len(new_zoo))
print('All animals in new zoo are', new_zoo)
print('Animals brought from old zoo are', new_zoo[2])
print('Last animal brought from old zoo is', new_zoo[2][2])
print('Number of animals in the new zoo is',
len(new_zoo)-1+len(new_zoo[2]))
輸出:
$ python using_tuple.py
Number of animals in the zoo is 3
Number of cages in the new zoo is 3
All animals in new zoo are ('monkey', 'camel', ('python', 'elephant', 'penguin'))
Animals brought from old zoo are ('python', 'elephant', 'penguin')
Last animal brought from old zoo is penguin
Number of animals in the new zoo is 5
代碼如何工作:
變量zoo引用一個元組。我們看到len函數可以得到元組的長度。這也表明元組同樣是一個序列類型。
因為老動物園歇菜了,於是我們將這些動物轉移到一個新的動物園。因此元組new_zoo既包含已有的動物又包含從老動物園轉移過來的新動物。
言歸正傳,注意一個包含在其它元組內的元組並不會丟失它的身份。
(注:包含元組會引用被包含元組,即在包含元組內對被包含元組的操作會反應到被包含元組自身之上,有點繞口。。。)
像列表一樣,我們可以通過一對方括號指定元素的位置訪問這個元素。這叫做索引操作符。
我們通過new_zoo[2]訪問new_zoo的第三個元素,通過new_zoo[2][2]訪問new_zoo的第三個元素的第三個元素。
一但你理解這種語言風格,這樣的操作太安逸了。
小括號
雖然小括號是可選的,但我強烈建議你堅持使用小括號,這樣一眼就能看出它是個元組,尤其還能避免出現歧義。
例如,print(1, 2, 3)和print((1, 2, 3))是不同的 – 前者打印3個數字而后者打印一個元組(包含3個數字)。
擁有0個或1個元素的元組
一個空元組通過空小括號創建,例如myempty = ()。
不過,指定一個單元素元組就不那么直觀了。你必須在其僅有的一個元素后跟隨一個逗號,這樣python才能區分出
你要的是元組而不是一個被小括號包圍的對象的表達式。例如你想要一個包含值為2的單元素元組,則必須寫成singleton = (2, )
perl程序員請注意(注:對不起perl程序員,我是perl盲。。。不知道說的啥,以后可能補充翻譯)
A list within a list does not lose its identity i.e. lists are not flattened as in Perl. The
same applies to a tuple within a tuple, or a tuple within a list, or a list within a tuple,
etc. As far as Python is concerned, they are just objects stored using another object,
that's all.
字典
字典就像通訊錄,只要知道聯系人的名字就能找到他的地址或詳細信息。即我們將鍵(名字)與值(相關信息)聯系到一起。
注意鍵必須是唯一的,這就像如果兩個人同名你就沒法找到正確的信息了。
還有字典的鍵必須是不可變對象(比如字符串),但字典的值可以是可變或不可變對象。基本上這意味着只能將簡單的對象作為鍵。
字典中的鍵值對使用語法d = {key1 :value1, key2: value2}指定。
其中鍵和值由分號分隔而所有的鍵值對用逗號分隔,並且它們被括在一對大括號內。
記住字典中的鍵值對是無序的。如果你希望按照特定的順序排列它們,你只能在使用前自己排序。
而你實際使用的字典是dict類的對象/實例。
范例:
#!/usr/bin/python
# Filename: using_dict.py
# 'ab'是'a'ddress'b'ook的縮寫
ab = { 'Swaroop' : 'swaroop@swaroopch.com',
'Larry' : 'larry@wall.org',
'Matsumoto' : 'matz@ruby-lang.org',
'Spammer' : 'spammer@hotmail.com'
}
print("Swaroop's address is", ab['Swaroop'])
# 刪除一個鍵值對
del ab['Spammer']
print('/nThere are {0} contacts in the address-book/n'.format(len(ab)))
for name, address in ab.items():
print('Contact {0} at {1}'.format(name, address))
# 添加一個鍵值對
ab['Guido'] = 'guido@python.org'
if 'Guido' in ab: # OR ab.has_key('Guido')
print("/nGuido's address is", ab['Guido'])
Output:
$ python using_dict.py
Swaroop's address is swaroop@swaroopch.com
There are 3 contacts in the address-book
Contact Swaroop at swaroop@swaroopch.com
Contact Matsumoto at matz@ruby-lang.org
Contact Larry at larry@wall.org
Guido's address is guido@python.org
代碼如何工作:
我們使用先前介紹的語法創建字典ab。然后使用在列表和元組部分討論過的索引操作符指定字典鍵訪問鍵值對。多簡單的語法阿。
我們的老朋友del語句可以幫助我們刪除鍵值對。只需簡單的為索引操作符指定被刪除的鍵,再將其傳給del語句就哦了。
執行刪除操作時我們無需理會鍵所對應的值。
接下來我們使用字典的items方法訪問字典的鍵值對,它會返回一個包含鍵值對元組的列表 – 值跟在鍵后面。
在for…in循環中我們檢索每個鍵值對並將它們分別賦給變量name和address,之后在循環體中打印它們。
利用索引操作符訪問一個鍵並對其賦予一個值我們可以增加一個新的鍵值對,就象本例中的Guido那樣。
通過dict類的has_key可以檢查字典中是否存在某個鍵值對。你可以執行help(dict)找到字典所有方法的列表。
關鍵字實參與字典
如果你已經在函數中使用過關鍵字實參,那么你也已經使用過字典了!
你可以這樣理解 – 你在函數定義時的形參列表中指定了鍵值對,當你在函數中訪問這些變量的時候只不過是在訪問一個字典
(在編譯器設計的術語中這被稱作符號表)
序列
列表,元組和字符串都是序列的例子,但到底序列是啥呢?為什么它對我們的意義如此特別?
序列最主要的特點在於支持成員從屬測試(即,表達式中的in和not in操作)和索引操作。
其中索引操作允許我們直接地獲取序列中的指定元素。
以上說到的三種序列類型 – lists,tuples,strings還支持一種切片操作,允許我們得到序列的一個切片,即序列的部分。
范例:
#!/usr/bin/python
# Filename: seq.py
shoplist = ['apple', 'mango', 'carrot', 'banana']
name = 'swaroop'
# Indexing or 'Subscription' operation
print('Item 0 is', shoplist[0])
print('Item 1 is', shoplist[1])
print('Item 2 is', shoplist[2])
print('Item 3 is', shoplist[3])
print('Item -1 is', shoplist[-1])
print('Item -2 is', shoplist[-2])
print('Character 0 is', name[0])
# Slicing on a list
print('Item 1 to 3 is', shoplist[1:3])
print('Item 2 to end is', shoplist[2:])
print('Item 1 to -1 is', shoplist[1:-1])
print('Item start to end is', shoplist[:])
# Slicing on a string
print('characters 1 to 3 is', name[1:3])
print('characters 2 to end is', name[2:])
print('characters 1 to -1 is', name[1:-1])
print('characters start to end is', name[:])
Output:
$ python seq.py
Item 0 is apple
Item 1 is mango
Item 2 is carrot
Item 3 is banana
Item -1 is banana
Item -2 is carrot
Character 0 is s
Item 1 to 3 is ['mango', 'carrot']
Item 2 to end is ['carrot', 'banana']
Item 1 to -1 is ['mango', 'carrot']
Item start to end is ['apple', 'mango', 'carrot', 'banana']
characters 1 to 3 is wa
characters 2 to end is aroop
characters 1 to -1 is waroo
characters start to end is swaroop
代碼如何工作:
首先我們看看如何使用索引得到序列的單個元素。這也被稱作下標操作。
正如上面的代碼,每當你在序列旁的方括號中指定一個數字的時候,python會獲取這個索引所對應的序列元素。
回想一下,python的索引從0開始計算。因此shoplist[0]獲取序列shplist的第一個元素,而shoplist[3]獲取第四個元素。
索引也可以是負數,這時候位置將從序列尾開始計算。所以,shoplist[-1]引用序列的最后一個元素,shoplist[-2]為倒數第二個。
切片操作的使用方法是先指定序列名后跟一對方括號,其中包含一對可選的由分號分隔的數字。
注意這與你至今使用的索引操作非常相似。記住數字是可選的,但分號不可以省略。
切片操作中的第一個數字(分號前)指出切片的開始位置而第二個數字(分號后)指定將在哪個位置結束。
如果省略第一個數字則python將以序列的起點為開始處,而省略第二個數字時切片會停止在序列的結尾處。
注意切片將在開始處開始,結束於結尾處之前,即包括開始處但不包括結尾處。(注:比如a[1:10],返回的是a[1]到a[9]不包括a[10])。
因此,shoplist[1:3]開始於索引1,包括索引2但止於索引3,即返回一個包含兩個元素的切片。與之類似shoplist[:]將返回整個序列的拷貝。
你還能以負索引切片。負數代表從序列的末尾開始反向計算位置。例如shooplist[: -1]返回整個序列,但不包括未末的元素。
另外你還可以為切片提供第三個實參,它代表步長(默認為1)。
>>> shoplist = ['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::1]
['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::2]
['apple', 'carrot']
>>> shoplist[::3]
['apple', 'banana']
>>> shoplist[::-1]
['banana', 'carrot', 'mango', 'apple']
注意當步長為2時,我們得到索引為0,2…的元素,步長為3時得到0,3…,以此類推。
用python交互解釋器(這樣你能立即看到結果)嘗試切片的各種用法吧。
序列類型最棒的地方在於你能夠以相同的方式訪問元組,列表,字符串!
集合
集合是簡單對象的無序集合,適合當更關心集合中的元素是否存在而不是它們的順序或是它們出現的次數的時候。
使用集合,你可以測試從屬關系,是否一個集合是另一個集合的子集,或是尋找兩個集合的交集等等。
>>> bri = set(['brazil', 'russia', 'india'])
>>> 'india' in bri
True
>>> 'usa' in bri
False
>>> bric = bri.copy()
>>> bric.add('china')
>>> bric.issuperset(bri)
True
>>> bri.remove('russia')
>>> bri & bric # OR bri.intersection(bric)
{'brazil', 'india'}
代碼如何工作:
代碼幾乎是自說明的,因為它涉及到的基礎集合論知識我們已經在學校學過了。
引用
當你創建一個對象並將其賦給一個變量的時候,變量只是引用了這個對象,而變量並不代表這個對象本身!
換言之,變量名指向你的計算機內存的一部分,而這部分內存用於存儲實際的對象。這叫做名字到對象的綁定。
通常你不用關心這些,但你應該知道由於引用造成的一些微妙的影響。
范例:
#!/usr/bin/python
# Filename: reference.py
print('Simple Assignment')
shoplist = ['apple', 'mango', 'carrot', 'banana']
mylist = shoplist # mylist只是指向相同對象的另一個名字
del shoplist[0] # 我購買了第一個水果,所以把它從清單中刪除
print('shoplist is', shoplist)
print('mylist is', mylist)
# 注意列表shoplist和mylist打印了相同的內容,其中都不包括’apple’,因為它們指向的是相同的對象。
print('Copy by making a full slice')
mylist = shoplist[:] # 以全切片創造一個列表的完整拷貝
del mylist[0] # 刪除第一個元素
print('shoplist is', shoplist)
print('mylist is', mylist)
# 注意現在兩個列表指向不同的對象(注:回憶一下,切片操作會返回一個新的對象!)
Output:
$ python reference.py
Simple Assignment
shoplist is ['mango', 'carrot', 'banana']
mylist is ['mango', 'carrot', 'banana']
Copy by making a full slice
shoplist is ['mango', 'carrot', 'banana']
mylist is ['carrot', 'banana']
代碼如何工作:
大多數的解釋已經包含在注釋中了。
記住,如果你想創建一個諸如列表這樣的序列或復雜對象(不是象整數那樣的簡單對象)的拷貝,必須使用切片操作。
如果你只是簡單的用變量名指向另一個變量名,兩者實際上將引用相同的對象,如果你不注意這點將會招來麻煩!
Perl程序員請注意
記住對於列表的賦值語句並不會創建一個拷貝。必須使用分片操作創建序列的拷貝。
(注:實際上切片操作不是唯一的選擇,內見的工廠函數比如list,typle, set等都能達到同樣的目的)
關於字符串的更多知識
前面我們已經詳細討論過字符串了。在這里我們還會了解到什么呢?
呵呵,你知道字符串同樣是一種對象並擁有很多方法嗎? 從檢查字符串的一部分到刪除其中的空格應有盡有!
你在程序中使用的所有字符串都是str類的對象。下面的例子會演示str類中的一些有用的方法。全部方法的列表,參見help(str)。
范例:
#!/usr/bin/python
# Filename: str_methods.py
name = 'Swaroop' # 這是一個字符串對象
if name.startswith('Swa'):
print('Yes, the string starts with "Swa"')
if 'a' in name:
print('Yes, it contains the string "a"')
if name.find('war') != -1:
print('Yes, it contains the string "war"')
delimiter = '_*_'
mylist = ['Brazil', 'Russia', 'India', 'China']
print(delimiter.join(mylist))
Output:
$ python str_methods.py
Yes, the string starts with "Swa"
Yes, it contains the string "a"
Yes, it contains the string "war"
Brazil_*_Russia_*_India_*_China
代碼如何工作:
在這里我們看到了許多字符串方法的用法。
startswith方法用於確定字符串是否以指定的字符串開頭。而in操作檢查一個字符串是否是另一個字符串的一部分。
find方法用來尋找給定的字符串在字符串中的位置,如果沒找到對應的子串則返回-1。
str類還有一個簡潔的連接序列中每個字符串並返回連接后的字符串的方法join,其中每個字符串都將以指定的字符串分隔。
小結
我們已經詳細研究了python中的各種內建數據結構。這些數據結構對於編寫合理規模大小的程序是必須可少的。
現在我擁有了許多的python的基礎知識,下一步我們會看到如何設計和編寫一個實用的python程序。
