天天看點

簡明Python3教程 11.資料結構

簡介

資料結構基本上就是 – 可以将一些資料結合到一起的結構,換言之用于存儲一組相關的資料。

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)

輸出:

    $ 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)。

元組

元組用于儲存各種各樣的對象。它與清單很相似,但它缺少清單提供的大量功能。

清單的一個主要特點就象字元串一樣,它是不可變類型,也就是說你不可以修改元組。

元組通過一組以逗号分隔的元素定義,并以一個可選的小括号閉合。

元組通常用于這樣的情形,一個語句或一個使用者定義的函數能夠安全的假設其使用的一組值(即元組值)不會發生改變。

# 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類的對象/執行個體。

# Filename: using_dict.py

# 'ab'是'a'ddress'b'ook的縮寫

ab = {  'Swaroop'   : '[email protected]',

        'Larry'     : '[email protected]',

        'Matsumoto' : '[email protected]',

        'Spammer'   : '[email protected]'

    }

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'] = '[email protected]'

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 [email protected]

    There are 3 contacts in the address-book

    Contact Swaroop at [email protected]

    Contact Matsumoto at [email protected]

    Contact Larry at [email protected]

    Guido's address is [email protected]

我們使用先前介紹的文法建立字典ab。然後使用在清單和元組部分讨論過的索引操作符指定字典鍵通路鍵值對。多簡單的文法阿。

我們的老朋友del語句可以幫助我們删除鍵值對。隻需簡單的為索引操作符指定被删除的鍵,再将其傳給del語句就哦了。

執行删除操作時我們無需理會鍵所對應的值。

接下來我們使用字典的items方法通路字典的鍵值對,它會傳回一個包含鍵值對元組的清單 – 值跟在鍵後面。

在for…in循環中我們檢索每個鍵值對并将它們分别賦給變量name和address,之後在循環體中列印它們。

利用索引操作符通路一個鍵并對其賦予一個值我們可以增加一個新的鍵值對,就象本例中的Guido那樣。

通過dict類的has_key可以檢查字典中是否存在某個鍵值對。你可以執行help(dict)找到字典所有方法的清單。

關鍵字實參與字典

如果你已經在函數中使用過關鍵字實參,那麼你也已經使用過字典了!

你可以這樣了解 – 你在函數定義時的形參清單中指定了鍵值對,當你在函數中通路這些變量的時候隻不過是在通路一個字典

(在編譯器設計的術語中這被稱作符号表)

序列

清單,元組和字元串都是序列的例子,但到底序列是啥呢?為什麼它對我們的意義如此特别?

序列最主要的特點在于支援成員從屬測試(即,表達式中的in和not in操作)和索引操作。

其中索引操作允許我們直接地擷取序列中的指定元素。

以上說到的三種序列類型 – lists,tuples,strings還支援一種切片操作,允許我們得到序列的一個切片,即序列的部分。

# Filename: seq.py

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[:])

    $ 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)

>>> bri.remove('russia')

>>> bri & bric # OR bri.intersection(bric)

{'brazil', 'india'}

代碼幾乎是自說明的,因為它涉及到的基礎集合論知識我們已經在學校學過了。

引用

當你建立一個對象并将其賦給一個變量的時候,變量隻是引用了這個對象,而變量并不代表這個對象本身!

換言之,變量名指向你的計算機記憶體的一部分,而這部分記憶體用于存儲實際的對象。這叫做名字到對象的綁定。

通常你不用關心這些,但你應該知道由于引用造成的一些微妙的影響。

# Filename: reference.py

print('Simple Assignment')

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] # 删除第一個元素

# 注意現在兩個清單指向不同的對象(注:回憶一下,切片操作會傳回一個新的對象!)

    $ python reference.py

    Simple Assignment

    shoplist is ['mango', 'carrot', 'banana']

    mylist is ['mango', 'carrot', 'banana']

    Copy by making a full slice

    mylist is ['carrot', 'banana']

大多數的解釋已經包含在注釋中了。

記住,如果你想建立一個諸如清單這樣的序列或複雜對象(不是象整數那樣的簡單對象)的拷貝,必須使用切片操作。

如果你隻是簡單的用變量名指向另一個變量名,兩者實際上将引用相同的對象,如果你不注意這點将會招來麻煩!

Perl程式員請注意

記住對于清單的指派語句并不會建立一個拷貝。必須使用分片操作建立序列的拷貝。

(注:實際上切片操作不是唯一的選擇,内見的工廠函數比如list,typle, set等都能達到同樣的目的)

關于字元串的更多知識

前面我們已經詳細讨論過字元串了。在這裡我們還會了解到什麼呢?

呵呵,你知道字元串同樣是一種對象并擁有很多方法嗎? 從檢查字元串的一部分到删除其中的空格應有盡有!

你在程式中使用的所有字元串都是str類的對象。下面的例子會示範str類中的一些有用的方法。全部方法的清單,參見help(str)。

# 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))

    $ 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程式。

--------------Python書籍推薦-----------------

Python基礎教程-第2版.修訂版 

簡明Python3教程 11.資料結構

PYTHON核心程式設計

簡明Python3教程 11.資料結構

零基礎學Python

簡明Python3教程 11.資料結構