5. 数据结构

这一章描述了你已经学习过的一些细节,同时也增加了一些新的东西。

5.1 深入学习列表

列表的数据结构还有更多的方法。这里列出了列表对象的所有方法:

  • list.append(x)
    在列表的结尾添加一个元素。和 a[len(a):] = [x] 等价。

  • list.extend(iterable)
    通过从可遍历对象中添加所有的对象来扩展列表。和 a[len(a):] = [x] 等价。

  • 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.clear()
    删除列表中所有的元素。与 del a[:] 等价。

  • list.index(x[, start[, end]])
    返回以0开始计数的,第一个数值为 x 的元素的索引值。如果没有这个元素,就会产生一个数值错误 (ValueError)。

    可选参数 startend 可以解释为 slice 记号,它们用来限制列表的子序列的一个搜索范围。返回的索引值是相对于整个序列的开头而计算出来的,而不是 start 参数。

  • list.count(x)
    返回 x 在列表中出现的次数。

  • list.sort(Key=None, reverse=False)
    给列表中的元素排序 (参数用来自定义排序方式,更多解释参考 sorted())。

  • list.reverse()
    反转列表中所有的元素。

  • list.copy()
    返回列表的一个浅复制。与 a[:] 等价。

下面是一个使用了大部分列表方法的一个例子:

  1. >>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
  2. >>> fruits.count('apple')
  3. 2
  4. >>> fruits.count('tangerine')
  5. 0
  6. >>> fruits.index('banana')
  7. 3
  8. >>> fruits.index('banana', 4) # Find next banana starting a position 4
  9. 6
  10. >>> fruits.reverse()
  11. >>> fruits
  12. ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
  13. >>> fruits.append('grape')
  14. >>> fruits
  15. ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
  16. >>> fruits.sort()
  17. >>> fruits
  18. ['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
  19. >>> fruits.pop()
  20. 'pear'

你也许注意到了,insert, remove, 或者 sort 这样的方法仅仅修改了列表,但是没有打印返回值——它们默认返回了 None。这是 Python 中所有可变数据结构的一个设计原理。

注释:其他的语言会返回这个可变对象,允许使用方法链,比如 d->insert("a")->remove("b")->sort();

5.1.1 把列表当栈使用

列表方法使得把列表当栈使用变得很方便,在栈中,最后一个添加进去的元素是第一个拿到的元素 (后进先出, “last-in, first-out”)。使用 append() 把元素加入栈顶。想要拿到栈顶的元素,使用 pop() 方法,而不需要写一个特定的索引值。举个例子:

  1. >>> stack = [3, 4, 5]
  2. >>> stack.append(6)
  3. >>> stack.append(7)
  4. >>> stack
  5. [3, 4, 5, 6, 7]
  6. >>> stack.pop()
  7. 7
  8. >>> stack
  9. [3, 4, 5, 6]
  10. >>> stack.pop()
  11. 6
  12. >>> stack.pop()
  13. 5
  14. >>> stack
  15. [3, 4]

5.1.2 把列表当队列使用

同样,也可以把列表当做队列来使用,在队列中,第一个加入的元素就是第一个拿到的元素 (先进先出, “first-in, first-out”)。然而,列表的这种用途并不高效。在列表的末尾添加或者删除元素非常快,而从列表的开头插入或者删除元素就很慢了 (因为其他所有的元素都必须要一个一个的交换位置)。

为了实现一个队列,使用 collections.deque,这个方法设计出来可以快速的从列表的两端添加和删除元素。举个例子:

  1. >>> from collections import deque
  2. >>> queue = deque(["Eric", "John", "Michael"])
  3. >>> queue.append("Terry") # Terry arrives
  4. >>> queue.append("Graham") # Graham arrives
  5. >>> queue.popleft() # The first to arrive now leaves
  6. 'Eric'
  7. >>> queue.popleft() # The second to arrive now leaves
  8. 'John'
  9. >>> queue # Remaining queue in order of arrival
  10. deque(['Michael', 'Terry', 'Graham'])

5.1.3 列表解析

列表解析 提供了一种简明的创建列表的方式。一般的应用需要创建新的列表,在这个列表中,每一个元素都是一些用于另一个序列或者可遍历对象的每一个成员的操作的结果,或者是用于创建那些满足某一个条件的子序列的操作结果。

举个例子:

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

注意,这里创建了 (或者重写) 了一个名叫 x 的变量,这个变量在循环结束扥时候仍然存在。我们可以计算出一个平方项的列表而不使用任何副作用的操作:

  1. squares = list(map(lambda x: x**2, range(10)))

或者,等效的:

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

这个就更加言简意赅

列表解析由由一个方括号组成,方括号中有一个表达式,后面跟着一个 for 分句,然后后面可以有零个或者更多 for 或者 if 分句。列表解析的结果是一个新的列表,这个新的列表的结果由 for 和 if 分句前面的表达式计算出来。举个例子,下面的列表解析在元素不相等的情况下,把这两个列表组合起来了:

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

这和下面的代码等价:

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

注意 for 和 if 语句的顺序为何在两种代码中都是一样的。

如果表示是是一个 元组 (tuple,比如上一个例子中的 (x,y)),它必须放在圆括号中:

  1. >>> vec = [-4, -2, 0, 2, 4]
  2. >>> # 创建一个两倍数值的列表
  3. >>> [x*2 for x in vec]
  4. [-8, -4, 0, 4, 8]
  5. >>> # 筛选掉负数
  6. >>> [x for x in vec if x >= 0]
  7. [0, 2, 4]
  8. >>> # 用函数应用每个元素
  9. >>> [abs(x) for x in vec]
  10. [4, 2, 0, 2, 4]
  11. >>> # 用每个元素调用方法
  12. >>> freshfruit = [' banana', ' loganberry ', 'passion fruit ']
  13. >>> [weapon.strip() for weapon in freshfruit]
  14. ['banana', 'loganberry', 'passion fruit']
  15. >>> # 创建一个双元组的列表,如 (number, square)
  16. >>> [(x, x**2) for x in range(6)]
  17. [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
  18. >>> # 元组必须放在圆括号中,否则会产生一个错误
  19. >>> [x, x**2 for x in range(6)]
  20. File "<stdin>", line 1, in <module>
  21. [x, x**2 for x in range(6)]
  22. ^
  23. SyntaxError: invalid syntax
  24. >>> # 用两个 for 的列表解析展开一个列表
  25. >>> vec = [[1,2,3], [4,5,6], [7,8,9]]
  26. >>> [num for elem in vec for num in elem]
  27. [1, 2, 3, 4, 5, 6, 7, 8, 9]

列表解析可以包含复杂的表达式和嵌套函数:

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

5.1.4 嵌套列表解析

在一个列表解析中的最初的表达式可以是任意的表达式,包括其他的列表表解析。

考虑下面的一个 3x4 矩阵的一个例子,它用3个长度为 4 的列表实现的。

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

下面的列表解析将会转置行和列:

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

正如我们在前一个小节看到的,嵌套列表解析实在它后面的 for 语句的上下文中计算出来的,所以这个例子也就等价于:

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

这个一次和下面的一样:

  1. >>> transposed = []
  2. >>> for i in range(4):
  3. ... # 下面三行实现了嵌套列表解析
  4. ... transposed_row = []
  5. ... for row in matrix:
  6. ... transposed_row.append(row[i])
  7. ... transposed.append(transposed_row)
  8. ...
  9. >>> transposed
  10. [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

在现实世界中,你应该偏好使用内建的函数,而不是复杂的流程控制语句。zip() 函数在这种情况下就表现非常棒:

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

更多关于这一行的星号的细节,见 解包参数列表