小段落


5. 資料結構

這一章討論的內容有些你在之前已經看過,但我們會更深入的討論。另外,我們也會介紹一些新的東西。


5.1 列(Lists)(續)

列(list)這個資料型態有一些方法可以使用,底下我們就列出來一些常用的方法:

append(x)
在list的尾端加入一個成員,也可以用這個方法來寫 a[len(a):] = [x]

extend(L)
接受一個新的list的參數,然後把它加入到目前這個list的尾端,也可以寫作 a[len(a):] = L

insert(i, x)
在某個特定的位置加入一個成員。第一個參數是要加入的位置的index,所以 a.insert(0, x) 會加入在list的最前端,而 a.insert(len(a), x) 會在最後端加入,相等於 a.append(x)

remove(x)
拿掉第一個其值相等於 x. 的成員。如果整個list都沒有這個成員,那就會得到一個錯誤(error)。

pop([i])
從一個list中拿掉某個位置的成員,並且傳回這個被拿掉的成員。如果沒有傳入位置的index的話, a.pop() 會傳回這個list的最一個成員,同樣的這個成為會被從這個list之中拿掉。

index(x)
傳回第一個其值相等於 x 的成員之位置(index),如果整個list都沒有這個成員,那就會得到一個錯誤(error)。

count(x)
傳回在整個list裡面, x 出現了多少次。

sort()
針對list裡面的成員做排序。

reverse()
反轉整個list裡面成員的位置。

底下的這個例子使用了大部分的lsit的方法(method):

>>> a = [66.6, 333, 333, 1, 1234.5]
>>> print a.count(333), a.count(66.6), a.count('x')
2 1 0
>>> a.insert(2, -1)
>>> a.append(333)
>>> a
[66.6, 333, -1, 333, 1, 1234.5, 333]
>>> a.index(333)
1
>>> a.remove(333)
>>> a
[66.6, -1, 333, 1, 1234.5, 333]
>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.6]
>>> a.sort()
>>> a
[-1, 1, 66.6, 333, 333, 1234.5]


5.1.1 把列(Lists)當作堆積(Stacks)使用

由於有這些好用的方法,把列(list)當作堆積(stack)來使用是一件容易的事。Stacks的最後一個加入的成員是第一個被取出來的成員(後進先出``last-in, first-out''法則)。要在stack的最頂端加入一個成員可以使用 append() ,要從stack的最頂端取出一個成員可以用 pop() (不須加入參數)。例子如下:

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]


5.1.2 把列(Lists)當作佇列(Queues)使用

你也可以很方便的拿list來當作佇列(queues)來使用。Queues的特點是第一個加入的成員會是第一個被取出的成員(先進先出``first-in, first-out''法則)。要在queue的後端加入一個成員可以使用 append() ,要從queue的最前端取出一個成員可以使用 use pop() ,記得參數是 0 。例子如下:

>>> queue = ["Eric", "John", "Michael"]
>>> queue.append("Terry") # Terry arrives
>>> queue.append("Graham") # Graham arrives
>>> queue.pop(0)
'Eric'
>>> queue.pop(0)
'John'
>>> queue
['Michael', 'Terry', 'Graham']


5.1.3 功能式程式設計工具

有三個與list合用非常有用的內建工具函式: filter(), map(), 以及 reduce()

"filter( function, sequence)" 這個函式會傳回 一個sequence (如果可能的話其成員為同一資料型態),這個sequence裡面的成員都是將 sequence 裡面的的成員,一一傳入到 function( item) 所代表的函式後,傳回值為true的成員所組合而成。這個函式對於傳入的 sequence 有過濾的效果,如下例所示:

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]

"map( function, sequence)" 會針對 sequence 裡的各個成員呼叫 function(item) ,然後傳回個別成員呼叫之後傳回的結果。舉例來說,要計算一連串的立方值,我們可以如此做:

>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

我們也可以傳入不只一個sequence。如果傳入多個sequence的話,第一個函式名稱的參數要是能夠處理多個參數的函式,然後系統會把各個sequence相對應的成員拿出來放入函式之中(如果兩個sequence長度不相等的話,不足的會用 None 來補足)。如果第一個函式名稱參數為 None 的話,所呼叫的函式就僅僅是傳回其所傳入的參數。

綜合以上的兩個特性,我們可以使用 " map(None, list1, list2)" 這一個工具函式來方便的轉換兩個sequence成為一個成對的成員組合的sequence。請看例子:

>>> seq = range(8)
>>> def square(x): return x*x
...
>>> map(None, seq, map(square, seq))
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36), (7, 49)]

"reduce( func, sequence)" 會利用 sequence 的前兩個成員當參數呼叫 func ,然後所得的傳回值再與下一個成員當參數傳入 func ,一直到整個 sequence 結束。下面的例子計算1到10的總和:

>>> def add(x,y): return x+y
...
>>> reduce(add, range(1, 11))
55

如果在 sequence 裡面只有一個成員的話,這個成員的值就會直接傳回。如果在 sequence 裡面沒有任何成員的話,會造成一個例外狀況(exception)。

我們也可以加入第三個參數來當作開始的值,如此當傳入的 sequence 是空的話,就可以使用這個開始值。如果是正常的sequencde的話,開始值會先跟第一個成員被傳入當作呼叫 func 的參數,其傳回值再跟第二個成員傳入 func ,依此類推。請看下例:

>>> def sum(seq):
... def add(x,y): return x+y
... return reduce(add, seq, 0)
...
>>> sum(range(1, 11))
55
>>> sum([])
0

5.1.4 傳回整個列 (List Comprehensions)

List comprehensions提供了一個製造list簡潔的方法,而不用使用 map(), filter() 以及/或者 lambda 形式。其結果也比使用以上的方法來做出list要來的清楚易懂。list comprehension通常是一個expression跟著是一個 for 的語句,然後是零個或多個 for 或是 if 語句。其傳回的list是一個由在 forif 語句條件下執行expression的結果。如果expression的結果是一個tuple,就必須用括號"( )"括起來。

>>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> vec = [2, 4, 6]
>>> [3*x for x in vec]
[6, 12, 18]
>>> [3*x for x in vec if x > 3]
[12, 18]
>>> [3*x for x in vec if x < 2]
[]
>>> [{x: x**2} for x in vec]
[{2: 4}, {4: 16}, {6: 36}]
>>> [[x,x**2] for x in vec]
[[2, 4], [4, 16], [6, 36]]
>>> [x, x**2 for x in vec] # error - parens required for tuples
File "<stdin>", line 1
[x, x**2 for x in vec]
^
SyntaxError: invalid syntax
>>> [(x, x**2) for x in vec]
[(2, 4), (4, 16), (6, 36)]
>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]
>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3]


5.2 del 敘述

del 敘述可以讓你輕鬆的去掉在list當中某一個位置(index)的成員。這個敘述也可以用切割(slice)的方法來去掉某一段的成員(在之前我們必須藉著設定某個slice為空list來達成同樣的效果)。請看下例:

>>> a
[-1, 1, 66.6, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.6, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.6, 1234.5]

del 也可以用來去掉整個變數:

>>> del a

如果你在去掉之後還繼續使用這個變數名稱的話,就會得到一個錯誤 (除非你之後再設定另外一個值給它)。我們稍後會繼續看到使用 del 的例子。


5.3 Tuples(固定有序列)及Sequences(有序列)

我們之前所討論的lists以及字串(strings)有很多的共通點,例如可以用index來定位置,可以切出其中的某一段(slicing)等等。事實上,list及字串都是 sequence 這個資料型態的特例。由於Python是一個可以不斷進步的語言,其他的sequence資料型態有可能會陸續的加入。我們就來看另外一種標準的sequence資料型態:固定有序列( tuple )。

一個tuple是由特定數目的值所組成,其成員與成員之間以逗號分開。舉例如下:

>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))

如同在前面例子所見到的,tuples輸出的結果都會包含在括弧之中。所以,巢狀tuple(tuple之中有tuple)可以被清楚的區分出來。Tuple在輸入的時候可以有括弧也可以沒有,通常我們都會加上括弧(特別是用在在複雜的expression之中)。

Tuples有很多種用途,例如(x, y)座標,從資料庫取出的員工的資料庫記錄等等。Tuples跟字串一樣都是不可改變(immutable)的:我們不能單獨的設定一個tuple裡面的個別成員(雖然我們可以用切割及連結(concaatenation)來達到同樣的效果)。我們也可以做出可以改變成員的tuple來,例如list。

有一個特殊的情況就是只包含0個或1個成員的tuple:要創造這樣的一個tuple,我們必須在語法上有一些的變化。空的tuple的表示方法是一對空的括弧,只有一個成員的tuple表示方法是在成員後面加上逗點(不能只是用括弧把一個成員括起來)。雖然有點奇怪,但是蠻有用的。請看例子:

>>> empty = ()
>>> singleton = 'hello', # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)

t = 12345, 54321, 'hello!' 這個敘述是一個tuple包裝( tuple packing )的例子: 12345 , 54321 以及 'hello!' 這三個值都被包裝放在一個tuple裡面了。我們也可以使用相反的操作方式,例如:

>>> x, y, z = t

這個動作叫做打開sequence( sequence unpacking )。Sequence unpacking的動作需要在設定符號左邊有一串的變數,其數目應與右邊sequence的成員數目相同。值得注意的是,多重設定(a, b = 1, 2)其實只是tuple packing以及sequence unpacking的結合罷了!

有一個不太對稱的地方:packing的動作永遠結果是一個tuple,但是unpacking可以針對各種不同的sequence來做。


5.4 Dictionaries(字典)

另外一個在Python當中很好用的內建資料型態是字典( dictionary )。Dictionary有的時候在別的程式語言裡面也叫做連結記憶( ``associative memories'' )或者是連結陣列( ``associative arrays'' )。不同於sequence是由一連串的數字來做index,dictionary用一個特殊的不可改變的(immutable)鑰( keys 來當作其 index。字串及數字都不能被改變,所以都可以來當作dictionary的key。Tuple如果只含有字串,數目字,以及其他tuple的話也可以當作key。如果tuple裡面有包含任何可改變的(mutable)的物件的話(包括直接或間接),就不能當作key來使用。List不能當作key,因為list的成員可以被改變(你可以用 append() 以及 extend() 之類的方法,或是切割(slicing) 或 index 來設定list的個別成員)。

我們最好把dictionary想像成一個沒有順序的 key: value 成對的組合。唯一的條件是,在dictionary裡面key的值必須是唯一不重複的。最簡單的dictionary就是一對空的中括弧: {} 。在中括弧裡面放入由逗號分開的key:value對,就成了dictionary裡面的成員。這也是當dictionary被印到輸出時的標準格式。

我們可以對dictionary做一些事,包括加入一個帶有key的值、或者是用key來找一個特殊的值。我們也可以用 del 來刪去一對key:value的成員。如果你試圖存一對key:value但是這個key已經被使用了的話,原來的那一個value的值就會被蓋過。如果你想用一個不存在的key來找出某一個成員的話,你會得到一個error。

使用 keys() 這一個dictionary的方法我們可以得到一個由所有的key值組成的list,其順序是隨機沒有次序的(如果你想要排序的話,只要針對這一個得到的list來呼叫其 sort() 方法就可以了)。要檢查某個key是不是存在的話,你可以使用 has_key() 這一個method來做檢查。

底下是一個有關dictionary的小例子:

>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}
>>> tel.keys()
['guido', 'irv', 'jack']
>>> tel.has_key('guido')
1


5.5 條件(續)

在之前所談到的 whileif 裡面的條件敘述,除了一般的比較之外也可以包含其他的運算。

我們可以用 in 以及 not in 來檢查某個值是否出現(或沒出現)在某個sequence裡面。我們也可以使用 is 以及 is not 來檢查兩個物件是否事實上指的是相同的一個物件(這只有跟像是list一樣可變的物件有關)。所有的比較運算的運算優先次序都是一樣的,都比所有的數字運算要來的低。

比較運算是可以連起來的:像是 a < b == c 就是試驗是否 ab 小,以及 bc 是否相等。

比較運算也可以用 and 以及 or 等boolean運算來連結起來,其比較的結果(或其他boolean運算的結果)也可以用 not 來得到相反(negated)的結果。在這些運算裡, not 有最高的優先次序, or 的優先次序最低,但是它們所有的優先次序都比比較運算來的低。所以, A and not B or C 其實相等於 (A and (not B)) or C 。當然,最好適時的使用括弧來幫助你表達你真正想要的組合。

and 以及 or 這兩個boolean運算元也可以稱做有捷徑的運算元( shortcut operators):它們的evaluated的次序都是由左而右,而且一但已經可以決定其運算的結果,就不會再繼續的做下去。也就是說如果 A 以及 C 都是 true 而 B 是false的話, A and B and C 並不會evaluate C 這個expression。一般來說這些shortcut operator 的傳回值如果不是當作boolean而是當作一般的值來用的話,其傳回值會是最後一個被evaluate的expression的值。

我們也可以把一個比較運算,或是 boolean運算的結果設定給一個變數,其例子如下:

>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'

值得注意的是不像是C,在Python裡面設定(assignment)不能夠放在expression裡面。C的程式設計師也許會針對此點抱怨,但是這樣的好處是可以避免一些常見的把設定( = )及等於( == )弄混淆的情形


5.6 Sequences(有序列)及其他資料型態的比較

Sequence 物件可以和其他的相同資料型態的sequence 物件相比較,其比較方法是依照所謂的 lexicographical 順序(lexicographical ordering)。首先是兩個sequence的第一個成員互相比較,如果比較有大小之別的話就此決定其相對大小,若是相等的話就再比較下一個成員的大小,餘此類推直到sequence的結束。如果兩個要相比較的成員本身也是一個sequence的話,同樣的條件可以繼續遞迴的使用在這兩個sequence之上。如果這兩個sequence的所有成員都相等的話,我們就說這兩個成員是相等的。如果某一個sequence是另一個sequence的一部份的話,較短的那一個sequence就是較小的。字串的Lexicographical順序用的是個別字元的ASCII碼的順序。底下是一些同一資料型態的sequence的比較例子:

(1, 2, 3)              < (1, 2, 4)
[1, 2, 3] < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4) < (1, 2, 4)
(1, 2) < (1, 2, -1)
(1, 2, 3) == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)

值得注意的是,我們也可以 比較兩個不同資料型態的物件,而其結果是依其資料型態的名稱來決定的。所以所有的list都比字串還要來的小(因為list小於string),所有的string也都比tuple還要小。至於數值的資料型態則是由其數值大小來決定其大小,所以0等於0.0的,其餘按此類推。 5.1



Footnotes

... 其餘按此類推。5.1
你不應該完全倚賴這些比較不同資料型態的規則,因為這些規則是尚未確定的,以後的Python版本也有可能會再做更動。

請看關於此文件… 裡面有關如何給我們建議的說明。