序列

序列允许你以有组织和高效的方式存储多个值。Python 中有七种序列型:字符串、字节、列表、元组、字节数组、缓冲区和范围对象。字典和集合是用于存储顺序数据的容器。

字符串

我们已经讨论过字符串,但那是在你知道什么是序列之前。其他语言中的数组元素以及字符串中的字符通常可以通过方括号或下标运算符来访问。在 Python 中也可以这样做:

>>> "Hello, world!"[0]
'H'
>>> "Hello, world!"[1]
'e'
>>> "Hello, world!"[2]
'l'
>>> "Hello, world!"[3]
'l'
>>> "Hello, world!"[4]
'o'

索引从 0 到 n-1 编号,其中 n 是项数(或字符数),它们是从字符串的开始位置依次分配给字符的:

 H  e  l  l  o  ,     w  o  r  l  d  !
 0  1  2  3  4  5  6  7  8  9 10 11 12

负数索引(从 -1 到 -n 编号)是从字符串的末尾开始计算的:

 H   e   l   l   o   ,       w   o   r  l  d  !
-13 -12 -11 -10 -9  -8  -7  -6  -5  -4 -3 -2 -1
>>> "Hello, world!"[-2]
'd'
>>> "Hello, world!"[-9]
'o'
>>> "Hello, world!"[-13]
'H'
>>> "Hello, world!"[-1]
'!'

如果给定的索引小于 -n 或大于 n-1,则会引发 IndexError

在 Python 中,冒号(:)允许方括号中使用两个数字。对于仅使用数字索引的序列,冒号可以返回指定索引之间的部分,这称为“切片”,切片字符串的结果通常被称为“子字符串”。

>>> "Hello, world!"[3:9]
'lo, wo'
>>> string = "Hello, world!"
>>> string[:5]
'Hello'
>>> string[-6:-1]
'world'
>>> string[-9:]
'o, world!'
>>> string[:-8]
'Hello'
>>> string[:]
'Hello, world!'

如上所示,如果省略第一个和第二个数字,它们将采用默认值,即 0 和 n-1,分别对应序列的开始和结束(在此例中)。还要注意,方括号的左侧是包含的,而右侧是排除的:在第一个例子中,[3:9] 中索引 3 的字符 'l' 被包含,而索引 9 的字符 'r' 被排除。

我们还可以在方括号中添加第三个数字,使用另一个冒号,这表示切片的增量步长:

>>> s = "Hello, world!"
>>> s[3:9:2]  # 返回子字符串 s[3]s[5]s[7]
'l,w'
>>> s[3:6:3]  # 返回子字符串 s[3]
'l'
>>> s[:5:2]  # 返回子字符串 s[0]s[2]s[4]
'Hlo'
>>> s[::-1]  # 返回反转后的字符串
'!dlrow ,olleH'
>>> s[::-2]  # 返回子字符串 s[-1]s[-3]s[-5]s[-7]s[-9]s[-11]s[-13]
'!lo olH'

增量步长可以是正数或负数。对于正增量步长,切片从左到右;对于负增量步长,切片从右到左。还需注意:

  • 默认增量步长为 1。
  • 当增量步长为负数时,第一个和第二个数字的默认值分别为 -1(或 n)和 -n(或 0),与增量步长为正数时的默认值相同。
  • 增量步长不能为 0。

列表

列表就是字面上的意思:一个有序的值的列表。使用方括号创建列表。例如,一个空列表可以这样初始化:

spam = []

列表中的值通过逗号分隔。例如:

spam = ["bacon", "eggs", 42]

列表可以包含不同型的对象。它可以同时包含字符串 "eggs" 和 "bacon",以及数字 42。

与字符串中的字符一样,列表中的项也可以通过从 0 开始的索引访问。要访问列表中的特定项,你可以通过列表的名字,后跟方括号中的项的编号来引用它。例如:

>>> spam
['bacon', 'eggs', 42]
>>> spam[0]
'bacon'
>>> spam[1]
'eggs'
>>> spam[2]
42

你也可以使用负数索引,它从列表的末尾向回计数:

>>> spam[-1]
42
>>> spam[-2]
'eggs'
>>> spam[-3]
'bacon'

len() 函数也可以用于列表,返回列表中项的数量:

>>> len(spam)
3

请注意,len() 函数计算的是列表中项的数量,因此 spam 中最后一项(42)的索引是 len(spam) - 1

列表中的项也可以像普通变量的内容一样被修改:

>>> spam = ["bacon", "eggs", 42]
>>> spam
['bacon', 'eggs', 42]
>>> spam[1]
'eggs'
>>> spam[1] = "ketchup"
>>> spam
['bacon', 'ketchup', 42]

(字符串是不可变的,无法修改。)像字符串一样,列表也可以进行切片操作:

>>> spam[1:]
['eggs', 42]
>>> spam[:-1]
['bacon', 'eggs']

也可以向列表中添加项。添加项有多种方式,最简单的方法是使用列表的 append() 方法:

>>> spam.append(10)
>>> spam
['bacon', 'eggs', 42, 10]

请注意,你不能通过指定超出范围的索引手动插入元素。以下代码会失败:

>>> spam[4] = 10
IndexError: list assignment index out of range

相反,你必须使用 insert() 函数。如果你想在列表的某个索引处插入一个项,可以使用列表的 insert() 方法,例如:

>>> spam.insert(1, 'and')
>>> spam
['bacon', 'and', 'eggs', 42, 10]

你也可以使用 del 语句从列表中删除项:

>>> spam
['bacon', 'and', 'eggs', 42, 10]
>>> del spam[1]
>>> spam
['bacon', 'eggs', 42, 10]
>>> spam[0]
'bacon'
>>> spam[1]
'eggs'
>>> spam[2]
42
>>> spam[3]
10

如你所见,列表会自动重新排序,因此不会出现索引上的空缺。

列表有一个特殊的特性。给定两个列表 ab,如果你将 b 设置为 a,然后修改 ab 也会被改变:

>>> a=[2, 3, 4, 5]
>>> b=a
>>> del a[3]
>>> print(a)
[2, 3, 4]
>>> print(b)
[2, 3, 4]

这可以通过使用 b = a[:] 来轻松解决。

有关列表的更多解释,或要了解如何创建二维数组,请参见《数据结构/列表》部分。

元组

元组似于列表,但它们是不可变的。一旦设置了元组,就无法对其进行任何更改:你不能添加、修改或删除元组中的元素。否则,元组的工作方式与列表完全相同。

声明元组时使用逗号:

unchanging = "rocks", 0, "the universe"

通常,需要使用括号来区分不同的元组,例如在同一行进行多重赋值时:

foo, bar = "rocks", 0, "the universe"  # 这里有3个元素,但会失败——值太多
foo, bar = "rocks", (0, "the universe")  # 这里有2个元素,因为第二个元素是一个元组

不必要的括号可以使用而不会有害,但嵌套括号表示嵌套元组:

>>> var = "me", "you", "us", "them"
>>> var = ("me", "you", "us", "them")

两者都会产生相同的输出:

>>> print(var)
('me', 'you', 'us', 'them')

但是:

>>> var = ("me", "you", ("us", "them"))
>>> print(var)
('me', 'you', ('us', 'them'))  # 这是一个包含3个元素的元组,其中最后一个元素本身是一个元组

有关元组的更多解释,请参见《数据结构/元组》。

字典

字典也像列表一样,但它们是可变的——你可以向字典中添加、修改或删除元素。然而,字典中的元素不是像列表那样与数字绑定的,而是每个元素都有一个键和值。通过调用字典的键,可以返回与该键相关联的值。你可以将列表视为一种特殊的字典,其中每个元素的键是数字,按数字顺序排列。

字典使用大括号声明,每个元素由键、冒号和值组成。例如:

>>> definitions = {"guava": "a tropical fruit", "python": "a programming language", "the answer": 42}
>>> definitions
{'python': 'a programming language', 'the answer': 42, 'guava': 'a tropical fruit'}
>>> definitions["the answer"]
42
>>> definitions["guava"]
'a tropical fruit'
>>> len(definitions)
3

此外,向字典中添加元素也非常简单:只需像声明变量一样声明它:

>>> definitions["new key"] = "new value"
>>> definitions
{'python': 'a programming language', 'the answer': 42, 'guava': 'a tropical fruit', 'new key': 'new value'}

有关字典的更多解释,请参见《数据结构/字典》。

集合

集合与列表似,只是它们是无序的,且不允许重复值。集合中的元素既不绑定到数字(像列表和元组那样),也不绑定到键(像字典那样)。使用集合的原因是,集合在处理大量项目时比列表或元组要快得多,且集合提供快速的数据插入、删除和成员测试。集合还支持数学集合操作,如测试子集以及求两个集合的并集或交集。

>>> mind = set([42, 'a string', (23, 4)])  # 同样也可以使用 {42, 'a string', (23, 4)}
>>> mind
set([(23, 4), 42, 'a string'])
>>> mind = set([42, 'a string', 40, 41])
>>> mind
set([40, 41, 42, 'a string'])
>>> mind = set([42, 'a string', 40, 0])
>>> mind
set([40, 0, 42, 'a string'])
>>> mind.add('hello')
>>> mind
set([40, 0, 42, 'a string', 'hello'])

请注意,集合是无序的,添加到集合中的项目将会放置在一个无法确定的位置,而且这个位置可能会时常发生变化。

>>> mind.add('duplicate value')
>>> mind.add('duplicate value')
>>> mind
set([0, 'a string', 40, 42, 'hello', 'duplicate value'])

集合不能包含单一值的重复项。与列表可以包含任何型的元素不同,集合的数据类型是有限制的。集合只能包含可哈希的、不可变的数据类型。整数、字符串和元组是可哈希的;而列表、字典和其他集合(除了冻结集合,见下文)则不可哈希。

冻结集合

冻结集合和集合的关系就像元组和列表的关系一样。冻结集合是集合的不可变版本。例如:

>>> frozen = frozenset(['life', 'universe', 'everything'])
>>> frozen
frozenset(['universe', 'life', 'everything'])

其他数据类型

Python 还有其他型的序列,尽管这些型使用得不那么频繁,而且需要从标准库中导入才能使用。我们这里只是简单提及这些型。

  • array:一个型化列表,数组只能包含同质值。
  • collections.defaultdict:一个字典,当元素不存在时,返回默认值,而不是错误
  • collections.deque:一个双端队列,允许在队列的两端快速操作。
  • heapq:一个优先队列。
  • Queue:一个线程安全的多生产者、多消费者队列,用于多线程程序中。注意,列表在单线程代码中也可以作为队列使用。

有关集合的更多解释,请参见《数据结构/集合》。

第三方数据结构

一些有用的 Python 数据类型不包含在标准库中。这些型的用途非常专业。我们将提到一些更常见的第三方型。

  • numpy.array:用于重度数字运算,参见 numpy 部分。
  • sorteddict:顾名思义,它是一个排序字典。

练习

  1. 写一个程序,将 5、10 和 "twenty" 放入列表中。然后从列表中删除 10。
  2. 写一个程序,将 5、10 和 "twenty" 放入元组中。
  3. 写一个程序,将 5、10 和 "twenty" 放入集合中。故意以不同的顺序将 "twenty"、10 和 5 放入另一个集合。打印出这两个集合,并注意它们的排序。
  4. 写一个程序,构建一个元组,其中一个元素是冻结集合。
  5. 写一个程序,创建一个字典,将 1 映射到 "Monday",2 映射到 "Tuesday" 等等。
最后修改: 2025年01月30日 星期四 23:11