Python 容器:列表和元组

Published on 2017 - 01 - 20

大多数编程语言都有特定的数据结构来存储由一系列元素组成的序列,这些元素以它们所处的位置为索引:从第一个到最后一个依次编号。

除字符串外,Python 还有另外两种序列结构:元组和列表。它们都可以包含零个或多个元素。与字符串不同的是,元组和列表并不要求所含元素的种类相同,每个元素都可以是任何 Python 类型的对象。得益于此,你可以根据自己的需求和喜好创建具有任意深度及复杂度的数据结构。

为什么 Python 需要同时设定列表和元组这两种序列呢?这是因为元组是不可变的,当你给元组赋值时,这些值便被固定在了元组里,再也无法修改。然而,列表却是可变的,这意味着可以随意地插入或删除其中的元素。在后面的内容中,我会举许多关于这两种结构的例子,但重点会放在列表上。

列表

列表非常适合利用顺序和位置定位某一元素,尤其是当元素的顺序或内容经常发生改变时。与字符串不同,列表是可变的。你可以直接对原始列表进行修改:添加新元素、删除或覆盖已有元素。在列表中,具有相同值的元素允许出现多次。

使用[]或list()创建列表

列表可以由零个或多个元素组成,元素之间用逗号分开,整个列表被方括号所包裹:

>>> empty_list = [ ]
>>> weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
>>> big_birds = ['emu', 'ostrich', 'cassowary']
>>> first_names = ['Graham', 'John', 'Terry', 'Terry', 'Michael']

也可以使用 list() 函数来创建一个空列表:

>>> another_empty_list = list()
>>> another_empty_list
[]

使用list()将其他数据类型转换成列表

Python 的 list() 函数可以将其他数据类型转换成列表类型。下面的例子将一个字符串转换成了由单个字母组成的列表:

>>> list('cat')
['c', 'a', 't']

接下来的例子将一个元组(在列表之后介绍)转换成了列表:

>>> a_tuple = ('ready', 'fire', 'aim')
>>> list(a_tuple)
['ready', 'fire', 'aim']

使用 split() 可以依据分隔符将字符串切割成由若干子串组成的列表:

>>> birthday = '1/6/1952'
>>> birthday.split('/')
['1', '6', '1952']

如果待分割的字符串中包含连续的分隔符,那么在返回的列表中会出现空串元素:

>>> splitme = 'a/b//c/d///e'
>>> splitme.split('/')
['a', 'b', '', 'c', 'd', '', '', 'e']

如果把上面例子中的分隔符改成 // 则会得到如下结果:

>>> splitme = 'a/b//c/d///e'
>>> splitme.split('//')
>>>
['a/b', 'c/d', '/e']

使用[offset]获取元素

和字符串一样,通过偏移量可以从列表中提取对应位置的元素:

>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes[0]
'Groucho'
>>> marxes[1]
'Chico'
>>> marxes[2]
'Harpo'

同样,负偏移量代表从尾部开始计数:

>>> marxes[-1]
'Harpo'
>>> marxes[-2]
'Chico'
>>> marxes[-3]
'Groucho'
>>>

包含列表的列表

列表可以包含各种类型的元素,包括其他列表,如下所示:

>>> small_birds = ['hummingbird', 'finch']
>>> extinct_birds = ['dodo', 'passenger pigeon', 'Norwegian Blue']
>>> carol_birds = [3, 'French hens', 2, 'turtledoves']
>>> all_birds = [small_birds, extinct_birds, 'macaw', carol_birds]

all_birds 这个列表的结构是什么样子的?

>>> all_birds
[['hummingbird', 'finch'], ['dodo', 'passenger pigeon', 'Norwegian Blue'], 'macaw',
[3, 'French hens', 2, 'turtledoves']]

来访问第一个元素看看:

>>> all_birds[0]
['hummingbird', 'finch']

第一个元素还是一个列表:事实上,它就是 small_birds,也就是创建 all_birds 列表时设定的第一个元素。以此类推,不难猜测第二个元素是什么:

>>> all_birds[1]
['dodo', 'passenger pigeon', 'Norwegian Blue']

和预想的一样,这是之前指定的第二个元素 extinct_birds。如果想要访问 extinct_birds 的第一个元素,可以指定双重索引从 all_birds 中提取:

>>> all_birds[1][0]
'dodo'

上面例子中的 [1] 指向外层列表 all_birds 的第二个元素,而 [0] 则指向内层列表的第一个元素。

使用[offset]修改元素

就像可以通过偏移量访问某元素一样,你也可以通过赋值对它进行修改:

>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes[2] = 'Wanda'
>>> marxes
['Groucho', 'Chico', 'Wanda']

与之前一样,列表的偏移量必须是合法有效的。

通过这种方式无法修改字符串中的指定字符,因为字符串是不可变的。列表是可变的,因此你可以改变列表中的元素个数,以及元素的值。

指定范围并使用切片提取元素

你可以使用切片提取列表的一个子序列:

>>> marxes = ['Groucho', 'Chico,' 'Harpo']
>>> marxes[0:2]
['Groucho', 'Chico']

列表的切片仍然是一个列表。

与字符串一样,列表的切片也可以设定除 1 以外的步长。下面的例子从列表的开头开始每 2 个提取一个元素:

>>> marxes[::2]
['Groucho', 'Harpo']

再试试从尾部开始提取,步长仍为 2:

>>> marxes[::-2]
['Harpo', 'Groucho']

利用切片还可以巧妙地实现列表逆序:

>>> marxes[::-1]
['Harpo', 'Chico', 'Groucho']

使用append()添加元素至尾部

传统的向列表中添加元素的方法是利用 append() 函数将元素一个个添加到尾部。假设前面的例子中我们忘记了添加 Zeppo,没关系,由于列表是可变的,可以方便地把它添加到尾部:

>>> marxes.append('Zeppo')
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Zeppo']

使用extend()或+=合并列表

使用 extend() 可以将一个列表合并到另一个列表中。一个好心人又给了我们一份 Marx 兄弟的名字列表 others,我们希望能把它加到已有的 marxes 列表中:

>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> others = ['Gummo', 'Karl']
>>> marxes.extend(others)
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Zeppo', 'Gummo', 'Karl']

也可以使用 +=:

>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> others = ['Gummo', 'Karl']
>>> marxes += others
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Zeppo', 'Gummo', 'Karl']

如果错误地使用了 append(),那么 others 会被当成一个单独的元素进行添加,而不是将其中的内容进行合并:

>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> others = ['Gummo', 'Karl']
>>> marxes.append(others)
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Zeppo', ['Gummo', 'Karl']]

这个例子再次体现了列表可以包含不同类型的元素。上面的列表包含了四个字符串元素以及一个含有两个字符串的列表元素。

使用insert()在指定位置插入元素

append() 函数只能将新元素插入到列表尾部,而使用 insert() 可以将元素插入到列表的任意位置。指定偏移量为 0 可以插入列表头部。如果指定的偏移量超过了尾部,则会插入到列表最后,就如同 append() 一样,这一操作不会产生 Python 异常。

>>> marxes.insert(3, 'Gummo')
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Gummo', 'Zeppo']
>>> marxes.insert(10, 'Karl')
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Gummo', 'Zeppo', 'Karl']

使用del删除指定位置的元素

校对员刚刚通知说 Gummo 确实是 Marx 兄弟的一员,但 Karl 并不是,因此我们需要撤销刚才最后插入的元素:

>>> del marxes[-1]
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Gummo', 'Zeppo']

当列表中一个元素被删除后,位于它后面的元素会自动往前移动填补空出的位置,且列表长度减 1。再试试从更新后的 marxes 列表中删除 'Harpo':

>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Gummo', 'Zeppo']
>>> marxes[2]
'Harpo'
>>> del marxes[2]
>>> marxes
['Groucho', 'Chico', 'Gummo', 'Zeppo']
>>> marxes[2]
'Gummo'

del 是 Python 语句,而不是列表方法——无法通过 marxes[-2].del() 进行调用。del 就像是赋值语句(=)的逆过程:它将一个 Python 对象与它的名字分离。如果这个对象无其他名称引用,则其占用空间也被会清除。

使用remove()删除具有指定值的元素

如果不确定或不关心元素在列表中的位置,可以使用 remove() 根据指定的值删除元素。再见了,Gummo:

>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Gummo', 'Zeppo']
>>> marxes.remove('Gummo')
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Zeppo']

使用pop()获取并删除指定位置的元素

使用 pop() 同样可以获取列表中指定位置的元素,但在获取完成后,该元素会被自动删除。 如果你为 pop() 指定了偏移量,它会返回偏移量对应位置的元素;如果不指定,则默认使 用 -1。因此,pop(0) 将返回列表的头元素,而 pop() 或 pop(-1) 则会返回列表的尾元素:

>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> marxes.pop()
'Zeppo'
>>> marxes
['Groucho', 'Chico', 'Harpo']
>>> marxes.pop(1)
'Chico'
>>> marxes
['Groucho', 'Harpo']

使用index()查询具有特定值的元素位置

如果想知道等于某一个值的元素位于列表的什么位置,可以使用 index() 函数进行查询:

>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> marxes.index('Chico')
1

使用in判断值是否存在

判断一个值是否存在于给定的列表中有许多方式,其中最具有 Python 风格的是使用 in:

>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> 'Groucho' in marxes
True
>>> 'Bob' in marxes
False

同一个值可能出现在列表的多个位置,但只要至少出现一次,in 就会返回 True:

>>> words = ['a', 'deer', 'a' 'female', 'deer']
>>> 'deer' in words
True

使用count()记录特定值出现的次数

使用 count() 可以记录某一个特定值在列表中出现的次数:

>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes.count('Harpo')
1
>>> marxes.count('Bob')
0

>>> snl_skit = ['cheeseburger', 'cheeseburger', 'cheeseburger']
>>> snl_skit.count('cheeseburger')
3

使用sort()重新排列元素

在实际应用中,经常需要将列表中的元素按值排序,而不是按照偏移量排序。Python 为此提供了两个函数:

  • 列表方法 sort() 会对原列表进行排序,改变原列表内容;
  • 通用函数 sorted() 则会返回排好序的列表副本,原列表内容不变。

如果列表中的元素都是数字,它们会默认地被排列成从小到大的升序。如果元素都是字符串,则会按照字母表顺序排列:

>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> sorted_marxes = sorted(marxes)
>>> sorted_marxes
['Chico', 'Groucho', 'Harpo']

sorted_marxes 是一个副本,它的创建并不会改变原始列表的内容:

>>> marxes
['Groucho', 'Chico', 'Harpo']

但对 marxes 列表调用列表函数 sort() 则会改变它的内容:

>>> marxes.sort()
>>> marxes
['Chico', 'Groucho', 'Harpo']

当列表中的所有元素都是同一种类型时(例如 marxes 中都是字符串),sort() 会正常工作。有些时候甚至多种类型也可——例如整型和浮点型——只要它们之间能够自动地互相转换:

>>> numbers = [2, 1, 4.0, 3]
>>> numbers.sort()
>>> numbers
[1, 2, 3, 4.0]

默认的排序是升序的,通过添加参数 reverse=True 可以改变为降序排列:

>>> numbers = [2, 1, 4.0, 3]
>>> numbers.sort(reverse=True)
>>> numbers
[4.0, 3, 2, 1]

使用len()获取长度

len() 可以返回列表长度:

>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> len(marxes)
3

使用=赋值,使用copy()复制

通过下面任意一种方法,都可以将一个列表的值复制到另一个新的列表中:

  • 列表 copy() 函数
  • list() 转换函数
  • 列表分片 [:]

测试初始时我们的列表叫作 a,然后利用 copy() 函数创建 b,利用 list() 函数创建 c,并使用列表分片创建 d:

>>> a = [1, 2, 3]
>>> b = a.copy()
>>> c = list(a)
>>> d = a[:]

再次注意,在这个例子中,b、c、d 都是 a 的复制:它们是自身带有值的新对象,与原始的 a 所指向的列表对象 [1, 2, 3] 没有任何关联。改变 a 不影响 b、c 和 d 的复制:

>>> a[0] = 'integer lists are boring'
>>> a
['integer lists are boring', 2, 3]
>>> b
[1, 2, 3]
>>> c
[1, 2, 3]
>>> d
[1, 2, 3]

元组

与列表类似,元组也是由任意类型元素组成的序列。与列表不同的是,元组是不可变的,这意味着一旦元组被定义,将无法再进行增加、删除或修改元素等操作。因此,元组就像是一个常量列表。

使用()创建元组

下面的例子展示了创建元组的过程,它的语法与我们直观上预想的有一些差别。

可以用 () 创建一个空元组:

>>> empty_tuple = ()
>>> empty_tuple
()

创建包含一个或多个元素的元组时,每一个元素后面都需要跟着一个逗号,即使只包含一个元素也不能省略:

>>> one_marx = 'Groucho',
>>> one_marx
('Groucho',)

如果创建的元组所包含的元素数量超过 1,最后一个元素后面的逗号可以省略:

>>> marx_tuple = 'Groucho', 'Chico', 'Harpo'
>>> marx_tuple
('Groucho', 'Chico', 'Harpo')

Python 的交互式解释器输出元组时会自动添加一对圆括号。你并不需要这么做——定义元组真正靠的是每个元素的后缀逗号——但如果你习惯添加一对括号也无可厚非。可以用括号将所有元素包裹起来,这会使得程序更加清晰:

>>> marx_tuple = ('Groucho', 'Chico', 'Harpo')
>>> marx_tuple
('Groucho', 'Chico', 'Harpo')

可以一口气将元组赋值给多个变量:

>>> marx_tuple = ('Groucho', 'Chico', 'Harpo')
>>> a, b, c = marx_tuple
>>> a
'Groucho'
>>> b
'Chico'
>>> c
'Harpo'

有时这个过程被称为元组解包

可以利用元组在一条语句中对多个变量的值进行交换,而不需要借助临时变量:

>>> password = 'swordfish'
>>> icecream = 'tuttifrutti'
>>> password, icecream = icecream, password
>>> password
'tuttifrutti'
>>> icecream
'swordfish'
>>>

tuple() 函数可以用其他类型的数据来创建元组:

>>> marx_list = ['Groucho', 'Chico', 'Harpo']
>>> tuple(marx_list)
('Groucho', 'Chico', 'Harpo')

元组与列表

在许多地方都可以用元组代替列表,但元组的方法函数与列表相比要少一些——元组没有 append()、insert(),等等——因为一旦创建元组便无法修改。既然列表更加灵活,那为什么不在所有地方都使用列表呢?原因如下所示:

  • 元组占用的空间较小
  • 你不会意外修改元组的值
  • 可以将元组用作字典的键
  • 命名元组可以作为对象的替代
  • 函数的参数是以元组形式传递的

参考文档