小段落


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版本也有可能会再做更动。

请看关于此文件… 里面有关如何给我们建议的说明。