5. 数�结构¶

本章详细讨论了你已�学过的一些知识,�样也添加了一些新内容。

5.1. 关于列表更多的内容¶

Python 的列表数�类型包�更多的方法。这里是所有的列表对象方法:

list.append(x)

把一个元素添加到链表的结尾,相当于 a[len(a):] = [x]。

list.extend(L)

将一个给定列表中的所有元素都添加到�一个列表中,相当于 a[len(a):] = L。

list.insert(i, x)

在指定�置�入一个元素。第一个�数是准备�入到其��的那个元素的索引,例如 a.insert(0, x) 会�入到整个链表之�,而 a.insert(len(a), x) 相当于 a.append(x)。

list.remove(x)

删除链表中值为 x 的第一个元素。如果没有这样的元素,就会返回一个错误。

list.pop([i])

从链表的指定�置删除元素,并将其返回。如果没有指定索引,a.pop() 返回最�一个元素。元素��从链表中被删除。(方法中 i 两边的方括�表示这个�数是�选的,而�是�求你输入一对方括�,你会�常在 Python 库�考手册中�到这样的标记。)

list.index(x)

返回链表中第一个值为 x 的元素的索引。如果没有匹�的元素就会返回一个错误。

list.count(x)

返回 x 在链表中出现的次数。

list.sort(cmp=None, key=None, reverse=False)

对链表中的元素就地进行排�(�数�以用�自定义排�方法,�考 sorted() 的更详细的解释)。

list.reverse()

就地倒排链表中的元素。

下�这个示例演示了链表的大部分方法:

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

也许大家会å�‘现åƒ� insert, remove 或者 sort 这些修改列表的方法没有打å�°è¿”回值 –它们返回 None。[1] 在 python 中对所有å�¯å�˜çš„æ•°æ�®ç±»åž‹è¿™æ˜¯ç»Ÿä¸€çš„设计原则。

5.1.1. 把链表当作堆栈使用¶

链表方法使得链表�以很方便的�为一个堆栈�使用,堆栈作为特定的数�结构,最先进入的元素最�一个被释放(�进先出)。用 append() 方法�以把一个元素添加到堆栈顶。用�指定索引的 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. 把链表当作队列使用¶

你也�以把链表当�队列使用,队列作为特定的数�结构,最先进入的元素最先释放(先进先出)。�过,列表这样用效率�高。相对�说从列表末尾添加和弹出很快;在头部�入和弹出很慢(因为为了一个元素,�移动整个列表中的所有元素)。

�实现队列,使用 collections.deque,它为在首尾两端快速�入和删除而设计。例如:

>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.popleft()                 # The first to arrive now leaves
'Eric'
>>> queue.popleft()                 # The second to arrive now leaves
'John'
>>> queue                           # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

5.1.3. 函数�编程工具¶

对于链表�讲,有三个内置函数�常有用: filter(),map() 以� reduce()。

filter(function, sequence) 返回一个 sequence(�列),包括了给定�列中所有调用 function(item) �返回值为 true 的元素(如果�能的�,会返回相�的类型)。如果该 �列 (sequence)是一个 str, unicode 或者 tuple,返回值必定是�一类型,�则,它总是 list。例如,以下程��以计算一个被 3 或者 5 整除的�列:

>>> def f(x): return x % 3 == 0 or x % 5 == 0
...
>>> filter(f, range(2, 25))
[3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24]

map(function, 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]

�以传入多个�列,函数也必须�有对应数�的�数,执行时会�次用��列上对应的元素�调用函数(如果�些�列比其它的短,就用 None �代替)。如果把 None �为一个函数传入,则直接返回�数�为替代。例如:

>>> seq = range(8)
>>> def add(x, y): return x+y
...
>>> map(add, seq, seq)
[0, 2, 4, 6, 8, 10, 12, 14]

reduce(function, sequence) 返回一个�值,它是这样构造的:首先以�列的�两个元素调用函数 function,�以返回值和第三个�数调用,�次执行下去。例如,以下程�计算 1 到 10 的整数之和:

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

如果�列中�有一个元素,就返回它,如果�列是空的,就抛出一个异常。

�以传入第三个�数作为�始值。如果�列是空的,就返回�始值,�则函数会先接收�始值和�列的第一个元素,然�是返回值和下一个元素,�此类推。例如:

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

���示例中这样定义 sum():因为�计数值是一个通用的需求,早已有内置的 sum(sequence) 函数,�常好用。

5.1.4. 列表推导�¶

列表推导�为从�列中创建列表�供了一个简�的方法。普通的应用程�通过将一些�作应用于�列的�个�员并通过返回的元素创建列表,或者通过满足特定�件的元素创建��列。

例如,�设我们创建一个 squares 列表,�以�下�方�:

>>> squares = []
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

我们�样能够达到目的采用下�的方�:

squares = [x**2 for x in range(10)]

这也相当于 squares = map(lambda x: x**2, range(10)),但是上�的方�显得简�以�具有�读性。

列表推导�由包�一个表达�的括�组�,表达���跟�一个 for ��,之��以有零或多个 for 或 if ��。结果是一个列表,由表达���其��的 for 和 if ��上下文计算而�的结果构�。

例如,如下的列表推导�结�两个列表的元素,如果元素之间�相等的�:

>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

等�于:

>>> combs = []
>>> for x in [1,2,3]:
...     for y in [3,1,4]:
...         if x != y:
...             combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

值得注�的是在上�两个方法中的 for 和 if 语�的顺�。

如果想�得到一个元组 (例如,上�例�中的 (x, y)),必须�加上括�:

>>> vec = [-4, -2, 0, 2, 4]
>>> # create a new list with the values doubled
>>> [x*2 for x in vec]
[-8, -4, 0, 4, 8]
>>> # filter the list to exclude negative numbers
>>> [x for x in vec if x >= 0]
[0, 2, 4]
>>> # apply a function to all the elements
>>> [abs(x) for x in vec]
[4, 2, 0, 2, 4]
>>> # call a method on each element
>>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> # create a list of 2-tuples like (number, square)
>>> [(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
>>> # the tuple must be parenthesized, otherwise an error is raised
>>> [x, x**2 for x in range(6)]
  File "<stdin>", line 1, in ?
    [x, x**2 for x in range(6)]
               ^
SyntaxError: invalid syntax
>>> # flatten a list using a listcomp with two 'for'
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

列表推导��使用��的表达�和嵌套函数:

>>> from math import pi
>>> [str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']

5.1.4.1. 嵌套的列表推导�¶

列表推导��以嵌套。

考虑以下的 3x4 矩阵,一个列表中包�三个长度为4的列表:

>>> matrix = [
...     [1, 2, 3, 4],
...     [5, 6, 7, 8],
...     [9, 10, 11, 12],
... ]

现在,如果你想交�行和列,�以用嵌套的列表推导�:

>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

���看到的,嵌套的列表推导�是对 for ��的内容进行求值,所以上例就等价于:

>>> transposed = []
>>> for i in range(4):
...     transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

�过�说,如下也是一样的:

>>> transposed = []
>>> for i in range(4):
...     # the following 3 lines implement the nested listcomp
...     transposed_row = []
...     for row in matrix:
...         transposed_row.append(row[i])
...     transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

在实际中,你应该更喜欢使用内置函数组����程语�。对此�情况 zip() 函数将会�的更好:

>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

更多关于本行中使用的星�的说明,�考 �数列表的分拆 。

5.2. del 语�¶

有个方法�以从列表中按给定的索引而�是值�删除一个�项:del 语�。它��于有返回值的 pop() 方法。语� del 还�以从列表中删除切片或清空整个列表(我们以�介�过一个方法是将空列表赋值给列表的切片)。例如:

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

del 也�以删除整个��:

>>> del a

此��引用命� a 会引�错误(直到�一个值赋给它为止)。我们在��的内容中�以看到 del 的其它用法。

5.3. 元组和�列¶

我们知�链表和字符串有很多通用的属性,例如索引和切割�作。它们是�列类型(�� Sequence Types — str, unicode, list, tuple, bytearray, buffer, xrange )中的两�。因为 Python 是一个在�断进化的语言,也�能会加入其它的�列类型,这里介��一�标准�列类型:元组。

一个元组由数个逗�分隔的值组�,例如:

>>> 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 are immutable:
... t[0] = 88888
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> # but they can contain mutable objects:
... v = ([1, 2, 3], [3, 2, 1])
>>> v
([1, 2, 3], [3, 2, 1])

如你所�,元组在输出时总是有括�的,以便于正确表达嵌套结构。在输入时�以有或没有括�,�过�常括�都是必须的(如果元组是一个更大的表达�的一部分)。�能给元组的一个独立的元素赋值(尽管你�以通过�接和切割�模拟)。还�以创建包���对象的元组,例如链表。

虽然元组看起�类似于列表,它们�常用于��的场景和��的目的。元组是 ���的,通常包����类的元素并通过分拆(�阅本节��的内容)或索引访问(如果是 namedtuples,甚至�以通过属性)。列表是 ��的,它们的元素通常是相�的类型并通过迭代访问。

一个特殊的问题是构造包�零个或一个元素的元组:为了适应这�情况,语法上有一些�外的改�。一对空的括��以创建空元组;�创建一个�元素元组�以在值��跟一个逗�(在括�中放入一个�值�够明确)。丑陋,但是有效。例如:

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

语� t = 12345, 54321, 'hello!' 是 元组�装 (tuple packing)的一个例�:值 12345 , 54321 和 'hello!' 被�装进元组。其逆�作�能是这样:

>>> x, y, z = t

这个调用等��边�以是任何线性�列,称之为 �列拆� �常�当。�列拆��求左侧的��数目与�列的元素个数相�。�注�的是���数(multiple assignment)其实�是元组�装和�列拆�的一个结�。

5.4. 集�¶

Python 还包�了一个数�类型 set (集�)。集�是一个无����元素的集。基本功能包括关系测试和消除��元素。集�对象还支� union(��),intersection(交),difference(差)和 sysmmetric difference(对称差集)等数学�算。

大括�或 set() 函数�以用�创建集�。注�:想�创建空集�,你必须使用 set() 而�是 {}。�者用于创建空字典,我们在下一节中介�的一�数�结构。

以下是一个简�的演示:

>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> fruit = set(basket)               # create a set without duplicates
>>> fruit
set(['orange', 'pear', 'apple', 'banana'])
>>> 'orange' in fruit                 # fast membership testing
True
>>> 'crabgrass' in fruit
False

>>> # Demonstrate set operations on unique letters from two words
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a                                  # unique letters in a
set(['a', 'r', 'b', 'c', 'd'])
>>> a - b                              # letters in a but not in b
set(['r', 'd', 'b'])
>>> a | b                              # letters in either a or b
set(['a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'])
>>> a & b                              # letters in both a and b
set(['a', 'c'])
>>> a ^ b                              # letters in a or b but not both
set(['r', 'd', 'b', 'm', 'z', 'l'])

类似 列表推导�,这里有一�集�推导�语法:

>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}

5.5. 字典¶

�一个�常有用的 Python 内建数�类型是 字典 (�� Mapping Types — dict )。字典在�些语言中�能称为 ��内存 (associative memories) 或 ��数组 (associative arrays)。�列是以连续的整数为索引,与此��的是,字典以 关键字 为索引,关键字�以是任����类型,通常用字符串或数值。如果元组中�包�字符串和数字,它�以作为关键字,如果它直接或间接地包�了��对象,就�能当�关键字。�能用链表�关键字,因为链表�以用索引�切割或者 append() 和 extend() 等方法改�。

�解字典的最佳方�是把它看�无�的键:值对 (key:value 对)集�,键必须是互�相�的(在�一个字典之内)。一对大括�创建一个空的字典:{}。�始化链表时,在大括�内放置一组逗�分隔的键:值对,这也是字典输出的方�。

字典的主��作是��键�存储和��值。也�以用 del �删除键:值对(key:value)。如果你用一个已�存在的关键字存储值,以�为该关键字分�的值就会被�忘。试图从一个�存在的键中�值会导致错误。

对一个字典执行 keys() 将返回一个字典中所有关键字组�的无�列表(如果你想�排�,�需使用 sorted())。使用 in 关键字(指 Python 语法)�以检查字典中是�存在�个关键字(指字典)。

这里是使用字典的一个�示例:

>>> 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']
>>> 'guido' in tel
True

dict() 构造函数�以直接从 key-value 对中创建字典:

>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}

此外,字典推导��以从任�的键值表达�中创建字典:

>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}

如果关键字都是简�的字符串,有时通过关键字�数指定 key-value 对更为方便:

>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}

5.6. 循环技巧¶

在�列中循环时,索引�置和对应值�以使用 enumerate() 函数�时得到:

>>> for i, v in enumerate(['tic', 'tac', 'toe']):
...     print(i, v)
...
0 tic
1 tac
2 toe

�时循环两个或更多的�列,�以使用 zip() 整体打包:

>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
...     print 'What is your {0}?  It is {1}.'.format(q, a)
...
What is your name?  It is lancelot.
What is your quest?  It is the holy grail.
What is your favorite color?  It is blue.

需�逆�循环�列的�,先正�定��列,然�调用 reversed() 函数:

>>> for i in reversed(xrange(1, 10, 2)):
...     print(i)
...
9
7
5
3
1

�按排��的顺�循环�列的�,使用 sorted() 函数,它�改动原�列,而是生�一个新的已排�的�列:

>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
...     print f
...
apple
banana
orange
pear

�历字典时,使用 iteritems() 方法�以�时得到键和对应的值。:

>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.iteritems():
...     print k, v
...
gallahad the pure
robin the brave

若�在循环内部修改正在�历的�列(例如�制�些元素),建议您首先制作副本。在�列上循环�会��地创建副本。切片表示法使这尤其方便:

>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

5.7. 深入�件控制¶

while 和 if 语�中使用的�件�仅�以使用比较,而且�以包�任�的�作。

比较�作符 in 和 not in 用�判断值是�在一个区间之内。�作符 is 和 is not 比较两个对象是�相�;这�和诸如链表这样的��对象有关。所有的比较�作符具有相�的优先级,低于所有的数值�作。

比较�作�以传递。例如 a < b == c 判断是� a �于 b 并且 b 等于 c 。

比较�作�以通过逻辑�作符 and 和 or 组�,比较的结果�以用 not ���义。这些�作符的优先级�低于比较�作符,在它们之中,not 具有最高的优先级,or 优先级最低,所以 A and not B or C 等于 (A and (notB)) or C。当然,括�也�以用于比较表达�。

逻辑�作符 and 和 or 也称作 短路�作符:它们的�数从左��解�,一旦结果�以确定就�止。例如,如果 A 和 C 为真而 B 为�,A and B and C �会解� C。作用于一个普通的�逻辑值时,短路�作符的返回值通常是最�一个��。

�以把比较或其它逻辑表达�的返回值赋给一个��,例如:

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

需�注�的是 Python 与 C ��,在表达�内部�能赋值。C 程�员�常对此抱怨,�过它��了一类在 C 程�中�空�惯的错误:想�在解��中使 == 时误用了 = �作符。

5.8. 比较�列和其它类型¶

�列对象�以与相�类型的其它对象比较。比较�作按 字典� 进行:首先比较�两个元素,如果��,就决定了比较的结果;如果相�,就比较�两个元素,�此类推,直到所有�列都完�比较。如果两个元素本身就是�样类型的�列,就递归字典�比较。如果两个�列的所有�项都相等,就认为�列相等。如果一个�列是�一个�列的�始��列,较短的一个�列就�于�一个。字符串的字典�按照�字符的 ASCII 顺�。下�是�类型�列之间比较的一些例�:

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

注�比较��类型的对象也是�法的。比较的结果已�确定但是�一定��: 类型按其�称进行排�。因此,列表始终�于字符串,字符串总是�于元组,等等。[1] ��数值类型按照它们的值比较,所以 0 等于 0.0,等等。

Footnotes

[1]�应该�赖��类型对象的比较规则;它们在Python未�版本中�能�生�化。