第二章 列表和元组
本章将引入一个新的概念:数据结构。数据结构是通过某种方式(例如对元素进行编号)组织在一起的数据元素的集合,这些数据元素可以是数字或者字符,甚至可以是其他数据结构。在Python中,最基本的数据结构是序列(sequence),序列中的每个元素被分配一个序号——即元素的位置,也称为索引。第一个索引是0
,第二个则是1
,以此类推。
注:日常生活中,对某些东西计数或者编号的时候,可能会从1
开始。所以Python使用的编号机制可能看起来很奇怪,但这种方法其实非常自然。在后面的章节中可以看到,这样做的一个原因是也可以从最后一个元素开始计数;序列中的最后一个元素标记为-1
,倒数第二个元素为-2
,以此类推。这就意味着我们可以从第一个元素向前或向后计数了,第一个元素位于最开始,索引为0
.使用一段时间后,读者就会习惯于这种计数方式了。
本章首先对序列作一个概览,接下来讲解对所有序列(包括元组和列表)都通用的操作。这些操作也同样适用于字符串。尽管下一章才会全面介绍有关字符串操作的内容,但是本章的一些例子已经用到了字符串操作。
在完成了基本介绍后,会开始学习如何使用列表,同时看看它有什么特别之处。然后讨论元组。元组除了不能更改之外,其他的性质和列表都很类似。
2.1 序列概述
Python包含6中內建的序列,本章重点讨论最常用的两种类型:列表和元组。其他的內建序列类型有字符串(将在下一章再次讨论)、Unicode
字符串、buffer
对象和xrange
对象。
列表和元组的主要却别在于:列表可以修改,元组则不能。也就是说如果要根据要求来添加元素,那么列表可能会更好用;而处于某些原因,序列不能修改的时候,使用元组则更为合适。使用后者通常是技术性的,它与Python内部的运作方式有关。这也是內建函数会返回元组的原因。一般来说,在自己编写的程序中,几乎在所有的情况下都可以用列表代替元组(第四章将会介绍一个需要注意的例外情况:使用元组作为字典的键。在这种情况下,因为键不可更改,所以就不能使用列表)。
在需要操作一组数值的时候,序列很好用。可以用序列表示数据库中一个人的信息——第1个元素是姓名,第2个元素是年龄。根据上述内容编写一个列表(列表的各个元素通过逗号分隔,写在方括号中),如下所示:
>>> info = ["XuHoo", 19]
同时,序列也可以包含其他的序列,因此,构建如下的一个人员信息的列表也是可以的,这个列表就是你的数据库:
>>> user_1 = ["XuHoo", 19] >>> user_2 = ["Marlowes", 19] >>> database = [user_1, user_2] >>> database
[['XuHoo', 19], ['Marlowes', 19]]
注:Python之中还有一种名为容器(container)的数据结构。容器基本上是包含到其他对象的任意对象。序列(例如列表和元组)和映射(例如字典)是两类主要的容器。序列中的每个元素都有自己的编号,而映射中的每个元素则有一个名字(也称为键)。在第四章会介绍更多有关映射的知识。至于既不是序列也不是映射的容器类型,集合(set
)就是一个例子,请参见第十章的相关内容。
2.2 通用序列操作
所有序列类型都可以进行某些特定的操作。这些操作包括:索引(indexing)、分片(slicing)、加(adding)、乘(multiplying)以及检查某个元素是否属于序列的成员(成员资格)。除此之外,Python还有计算序列长度、找出最大元素和最小元素的內建函数。
注:本节有一个重要的操作没有提到——迭代(iteration)。对序列进行迭代的意思是:依次对序列中的每个元素重复执行某些操作。更多信息请参见5.5节。
2.2.1 索引
序列中的所有元素都是有编号的——从0
开始递增。这些元素可以通过编号分别访问,如下例所示:
>>> greeting = "Hello"
>>> greeting[0] 'H'
注:字符串就是一个由字符组成的序列。索引0
指向第1个元素,在这个例子中就是字母H
。
这就是索引。可以通过索引获取元素。所有序列都可以通过这种方式进行索引。使用负数索引时,Python会从右边,也就是从最后1个元素开始计数。最后1个元素的位置编号是-1
(不是-0
,因为那会和第1个元素重合):
>>> greeting[-1] 'o'
字符串字面值(就此而言,其他序列字面量亦可)能够直接使用索引,而不需要一个变量引用它们。两种做法的效果是一样的:
>>> "Hello"[-1] # String
'o'
>>> ["H", "e", "l", "l", "o"][-1] # List
'o'
如果一个函数调用返回一个序列,那么可以直接对返回结果进行索引操作。例如,假设你只对用户输入年份的第四个数字感兴趣,那么,可以进行如下操作:
>>> fourth = raw_input("Year: ")[3]
Year: 1997
>>> fourth '7'
代码清单2-1是一个示例程序,它要求输入年、月(1~12的数字)、日(1~31),然后打印出相应日期的月份名称,等等。
1 #!/usr/bin/env python
2 # coding=utf-8
3
4 # 根据给定的年月日,以数字形式打印出日期
5 months = [ 6 "January",
7 "February",
8 "March",
9 "April",
10 "May",
11 "June",
12 "July",
13 "August",
14 "September",
15 "October",
16 "November",
17 "December"
18 ]
19
20 # 以1~31的数字作为结尾的列表
21 endings = ["st", "nd", "rd"] + 17 * ["th"] \
22 + ["st", "nd", "rd"] + 7 * ["th"] \
23 + ["st"]
24
25 year = raw_input("Year: ")
26 month = raw_input("Month(1~12): ")
27 day = raw_input("Day(1~31): ")
28
29 month_number = int(month) 30 day_number = int(day)
31
32 # 记得要将月份和天数减1,以获得正确的索引
33 month_name = months[month_number - 1]
34 ordinal = day + endings[day_number - 1]
35
36 print month_name + " " + ordinal + ", " + year
Code_Listing 2-1
以下是程序执行的一部分结果:
Year: 1997 Month(1~12): 9 Day(1~31): 10 September 10th, 1997
2.2.2 分片
与使用索引来访问单个元素类似,可以使用分片操作来访问一定范围内的元素。分片通过冒号隔开的两个索引来实现:
>>> tag = '<a href="http://www.python.org">Python web site</a>'
>>> tag[9:30] 'http://www.python.org'
>>> tag[32:-4] 'Python web site'
分片操作对于提取序列的一部分是很用的。而编号在这里显得尤为重要。第1个索引是要提取的第1个元素的编号,而最后的索引则是分片之后剩余部分的第1个元素的编号。
>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> numbers[3:6]
[4, 5, 6] >>> numbers[0:1]
[1]
简而言之,分片操作的实现需要提供两个索引作为边界,第1个索引的元素是包含在分片内的,而第2个则不包含在分片内。
1. 优雅的捷径
假设需要访问最后3个元素(根据先前的例子),那么当然可以进行显示的操作:
>>> numbers[7:10]
[8, 9, 10]
现在,索引10
指向的是第11个元素——这个元素并不存在,却是在最后一个元素之后(为了让分片部分能够包含列表的最后一个元素,必须提供最后一个元素的下一个元素所对应的索引作为边界)。明白了吗?
现在,这样的做法是可行的。但是,如果需要从列表的结尾开始计数呢?
>>> numbers[-3:-1]
[8, 9]
看来并不能以这种方式访问最后的元素。那么使用索引0
作为最后一步的下一步操作所使用的元素,结果又会怎么样呢?
>>> numbers[-3:0]
[]
这并不是我们所要的结果。实际上,只要分片中最左边的索引比它右边的晚出现在序列中(在这个例子中是倒数第3个比第1个晚出现),结果就是一个空的序列。幸好,可以使用一个捷径:如果分片所得部分包括序列结尾的元素,那么,只需置空最后一个索引即可。
>>> numbers[-3:]
[8, 9, 10]
这种方法同样适用于序列开始的元素:
>>> numbers[:3]
[1, 2, 3]
实际上,如果需要复制整个序列,可以将两个索引都置空:
>>> numbers[:]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
代码清单2-2是一个小程序,它会提示输入URL(假设它的形式为 http://www.somedomainname.com ),然后提取域名。
1 #!/usr/bin/env python
2 # coding=utf-8
3
4 # 对http://www.something.com形式的URL进行分割
5
6 url = raw_input("Please enter the URL: ")
7 domain = url[11:-4]
8
9 print "Domain name: " + domain
Code_Listing 2-2
以下是程序运行的示例:
Please enter the URL: http://www.python.org
Domain name: python
2.更大的步长
进行分片的时候,分片的开始和结束点需要进行指定(不管是直接还是间接)。而另外一个参数(在Python2.3加入到内建类型)——步长(step length)——通常都是隐式设置的。在普通的分片中,步长是1
——分片操作就是按照这个步长逐个遍历序列的元素,然后返回开始和结束点之间的所有元素。
>>> numbers[0:10:1]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
在这个例子中,分片包含了另外一个数字。没错,这就是步长的显示设置。如果步长被设置为比1大的数,那么就会跳过某些元素。例如,步长设为2的分片包括的是从头开始到结束每隔1个的元素。
>>> numbers[0:10:2]
[1, 3, 5, 7, 9] >>> numbers[3:6:3]
[4]
之前提及的捷径也可以使用。如果需要将每4个元素中的第1个提取出来,那么只要将步长设置为4
即可:
>>> numbers[::4]
[1, 5, 9]
当然,步长不能为0
(那不会执行),但步长可以是负数,此时分片从右到左提取元素:
>>> numbers[8:3:-1]
[9, 8, 7, 6, 5]
>>> numbers[10:0:-2]
[10, 8, 6, 4, 2]
>>> numbers[0:10:-2]
[]
>>> numbers[::-2]
[10, 8, 6, 4, 2]
>>> numbers[5::-2]
[6, 4, 2]
>>> numbers[:5:-2]
[10, 8]
在这里要得到正确的分片结果需要动些脑筋。开始点的元素(最左边的元素)包括在结果之中,而结束点的元素(最右边的元素)则不在分片之内。当使用一个负数作为步长时,必须让开始点(开始索引)大于结束点。在没有明确指定开始点和结束点的时候,正负数的使用可能会带来一些混淆。不过在这种情况下Python会进行正确的操作:对于一个正数步长,Python会从序列的头部开始向右提取元素,直到最后一个元素;而对于负数步长,则是从序列的尾部开始向左提取元素,直到第一个元素。
2.2.3 序列相加
通过使用加运算符可以进行序列的连接操作:
>>> [1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6] >>> "Hello, " + "world!"
'Hello, world!'
>>> [1, 2, 3] + "wrold!" Traceback (most recent call last):
File "<stdin>", line 1, in <module> TypeError: can only concatenate list (not "str") to list
正如错误信息所提示的,列表和字符串是无法连接在一起的,尽管它们都是序列。简单来说,两种相同类型的序列才能进行连接操作。
2.2.4 乘法
用数字x
乘以一个序列会生成新的序列,而在新的序列中,原来的序列将被重复x
次。
>>> "Python" * 5
'PythonPythonPythonPythonPython'
>>> [19] * 10 [19, 19, 19, 19, 19, 19, 19, 19, 19, 19]
None、空列表和初始化
空列表可以简单地通过两个中括号进行表示([]
)——里面什么东西都没有。但是,如果想创建一个占用十个元素空间,却不包括任何有用内容的列表,又该怎么办呢?可以像前面那样使用[19]*10
,或者使用[0]*10
,这会更加实际一些。这样就生成了一个包括10个0的列表。然而,有时候可能会需要一个值来代表空值——意味着没有在里面放置任何元素。这个时候就需要使用None
。None
是一个Python的内建值,它的确切含义是“这里什么也没有”。因此,如果想初始化一个长度为10
的列表,可以按照下面的例子来实现:
>>> sequence = [None] * 10
>>> sequence
[None, None, None, None, None, None, None, None, None, None]
代码清单2-3的程序会在屏幕上打印一个由字符组成的“盒子”,而这个“盒子”在屏幕上居中而且能根据用户输入的句子自动调整大小。
代码可能看起来很复杂,但只使用基本的算法——计算出有多少个空格、破折号等字符,然后将它们放置到合适的位置即可。
1 #!/usr/bin/env python
2 # coding=utf-8
3
4 # 以正确的宽度在居中的“盒子”内打印一个句子
5
6 # 注意,整数除法运算符(//)只能用在Python2.2以及后续的版本,在之前的版本中,只能使用普通除法(/)
7
8 sentence = raw_input("Sentence: ")
9
10 screen_width = 80
11 text_width = len(sentence) 12 box_width = text_width + 6
13 left_margin = (screen_width - box_width) // 2
14
15 print
16 print " " * left_margin + "+" + "-" * (box_width - 2) + "+"
17 print " " * left_margin + "| " + " " * text_width + " |"
18 print " " * left_margin + "| " + sentence + " |"
19 print " " * left_margin + "| " + " " * text_width + " |"
20 print " " * left_margin + "+" + "-" * (box_width - 2) + "+"
21 print
Code_Listing 2-3
下面是该例子的运行情况:
Sentence: He's a very naughty boy!
+----------------------------+
| |
| He's a very naughty boy! |
| |
+----------------------------+
2.2.5 成员资格
为了检查一个值是否在序列中,可以使用in
运算符。该运算符和之前已经讨论过的(例如+
、*
运算符)有一点不同。这个运算符检查某个条件是否为真,然后返回相应的值:条件为真返回True
,条件为假返回False
。这样的运算符叫做布尔运算符,而返回的值叫做布尔值。第五章的条件语句部分会介绍更多关于布尔表达式的内容。
以下是一些使用了in运算符的例子:
>>> permissions = "rw"
>>> "w" in permissions
True >>> "x" in permissions
False >>> users = ["mlh", "foo", "bar"] >>> raw_input("Please enter your user name: ") in users # 用户名存在
Please enter your user name: mlh
True >>> raw_input("Please enter your user name: ") in users # 用户名不存在
Please enter your user name: Marlowes
False >>> subject = "$$$ Get rich now!!! $$$"
>>> "$$$" in subject
True
最初的两个例子使用了成员资格测试分别来检查"w"
和"x"
是否出现在字符串permissions
中。在UNIX系统中,这两行代码可以作为查看文件可写和可执行权限的脚本。接下来的例子则是检查所提供的用户名是否存在用户列表中。如果程序需要执行某些安全策略,那么这个检查就派上用场了(在这种情况下,可能还需要使用密码)。最后一个例子可以作为垃圾邮件过滤器的一部分,它可以检查字符串subject
是否包含字符串"$$$"
。
注:最后一个检查字符串是否包含"$$$"
的例子有些不同。一般来说,in
运算符会检查每一个对象是否为某个序列(或者是其他数据集合)的成员(也就是元素)。然后,字符串唯一的成员或者元素就是它的字符。下面的例子就说明了这一点:
>>> "P" in "Python" True
实际上,在早期的Python版本中,以上代码是唯一能用于字符串成员资格检查的方法——也就是检查某个字符是否存在于一个字符串中。如果尝试去检查更长的子字符串(例如"$$$"),那么会得到一个错误信息(这个操作会引发TypeError,即类型错误)。为了实现这个功能,我们必须使用相关的字符串方法。第三章会介绍更多相关的内容。但是从Python2.3起,in
运算符也能实现这个功能了。
代码清单2-4给出了一个查看用户输入的用户名和PIN码是否存在于数据库(实际上是一个列表)中的程序。如果用户名/PIN码这一数值对存在于数据库中,那么就在屏幕上打印"Access granted"
(第一章已经提到过if
语句,第五章还将对其进行全面讲解)。
1 #!/usr/bin/env python
2
3 # 检查用户名和PIN码
4
5 database = [
6 ["albert", "123"],
7 ["dilbert", "3521"],
8 ["smith", "6542"],
9 ["jones", "5634"]
10 ]
11
12 username = raw_input("Please enter your username: ")
13 pin = raw_input("Please enter your PIN: ")
14
15 if [username, pin] in database: 16 print "Access granted"
Code_Listing 2-4
2.2.6 长度、最小值和最大值
内建函数len
、min
和max
非常有用。len
函数返回序列中所包含元素的数量,min
函数和max
函数则分别返回序列中最大和最小元素(在第五章的“比较运算符”部分会更加详细介绍对象比较的内容)。
>>> numbers = [100, 34, 678] >>> len(numbers) 3
>>> max(numbers) 678
>>> min(numbers) 34
>>> max(2, 3) 3
>>> min(9, 3, 2, 5) 2
根据上述解释,我们可以很容易地理解例子中的各个操作是如何实现的,除了最后两个表达式可能会让人有些迷惑。在这里,max
函数和min
函数的参数并不是一个序列,而是以多个数字直接作为参数。
2.3 列表:Python的“苦力”
在前面的例子中已经用了很多次列表,它的强大之处不言而喻。本节会讨论列表不同于元组和字符串的地方:列表是可变的——可以改变列表的内容,并且列表有很多有用的、专门的方法。
2.3.1 list函数
因为字符串不能像列表一样被修改,所以有时根据字符串创建列表会很有用。list
函数(它实际上是一种类型而不是函数,但在这里两者的区别并不重要)可以实现这个操作:
>>> list("Hello")
['H', 'e', 'l', 'l', 'o']
注意,list
函数适用于所有类型的序列,而不只是字符串。
注:可以用下面的表达式将一个由字符(如前面代码中的)组成的列表转换为字符串:
''.join(somelist)
在这里,somelist
是需要转换的列表。要了解这行代码真正的含义,请参考第三章有关join
函数的部分。
2.3.2 基本的列表操作
列表可以使用所有适用于序列的标准操作,例如索引、分片、连接和乘法。有趣的是,列表是可以修改的。本节会介绍一些可以改变列表的方法:元素赋值、元素删除、分片赋值以及列表方法(请注意,并不是所有的列表方法都能真正地改变列表)。
1.改变列表:元素赋值
改变列表是很容易的,只需要使用第一章提到的普通赋值语句即可。然而,我们并不会使用x=2
这样的语句进行赋值,而是使用索引标记来为某个特定的、位置明确的元素赋值。如x[1]=2
。
>>> x = [1, 1, 1] >>> x[1] = 2
>>> x
[1, 2, 1]
注:不能为一个位置不存在的元素进行赋值。如果列表的长度为2
,那么不能为索引为100
的元素进行赋值。如果要那样做,就必须创建一个长度为101
(或者更长)的列表。请参考本章“None
、空列表和初始化”一节。
2.删除元素
从列表中删除元素也很容易:使用del
语句实现。
>>> names = ["Alice", "Beth", "Cecil", "Dee-Dee", "Earl"] >>> del names[2] >>> names
['Alice', 'Beth', 'Dee-Dee', 'Earl']
注意Cecil
是如何彻底消失的,并且列表的长度也从5
变为了4
。除了删除列表中的元素,del
语句还能用于删除其他元素。它可以用于字典元素(请参考第四章)甚至是其他变量得删除操作,有关这方面的详细介绍,请参见第五章。
3.分片赋值
分片是一个非常强大的特性,分片赋值操作则更加显现它的强大。
>>> name = list("Perl") >>> name
['P', 'e', 'r', 'l'] >>> name[2:] = list("ar") >>> name
['P', 'e', 'a', 'r']
程序可以一次为多个元素赋值了。可能有的读者会想:这有什么大不了的,难道就不能一次一个地赋吗?当然可以,但是在使用分片赋值时,可以使用与原序列不等长的序列将分片替换:
>>> name = list("Perl") >>> name[1:] = list("ython") >>> name
['P', 'y', 't', 'h', 'o', 'n']
分片赋值语句可以在不需要替换任何原有元素的情况下插入新的元素:
>>> numbers = [1, 5] >>> numbers[1:1] = [2, 3, 4] >>> numbers
[1, 2, 3, 4, 5]
这个程序只是“替换”了一个空的分片,因此实际的操作是插入了一个序列。以此类推,通过分片赋值来删除元素也是可行的。
>>> numbers
[1, 2, 3, 4, 5] >>> numbers[1:4] = [] >>> numbers
[1, 5]
上面的例子结果和del numbers[1:4]
的一样。接下来请读者自己尝试利用1之外的步长,甚至是负数进行分片吧。
2.3.3 列表方法
之前的章节中已经介绍了什么是函数,那么现在来看看另外一个与函数密切相关的概念——方法。
方法是一个与某些对象有紧密联系的函数,对象可能是列表、数字,也可能是字符串或者其他类型的对象。一般来说,方法可以这样进行调用:
对象.方法(参数)
除了对象被放置到方法名之前,并且两者之间用一个点号隔开,方法调用与函数调用很类似。第七章将对方法到底是什么进行更详细的解释。列表提供了几个方法,用于检查或者修改其中的内容。
1. append
append
方法用于在列表末尾追加新的对象:
>>> lst = [1, 2, 3] >>> lst.append(4) >>> lst
[1, 2, 3, 4]
为什么我选择了如此糟糕的变量名lst
,而不是使用list
来表示一个列表呢?原因在于list
是一个内建函数(实际上,从Python2.2开始,list
就是一个类型而不是函数了。(tuple
和str
也是如此)如果想了解完整的说明,请参见9.3.2节)。如果使用list
作为变量名,我就无法调用list
函数了。根据给定的应用程序可以定义更好的变量名,像lst
这样的变量名是毫无意义的。所以,如果需要定义一个价格列表,那么就应该使用prices
、prices_of_eggs
,或者pricesOfEggs
作为变量名。
注意,下面的内容很重要:append
方法和其他一些方法类似,只是在恰当位置修改原来的列表。这意味着,它不是简单地返回一个修改过的新列表——而是直接修改原来的列表。一般来说这正是你想要的,但是在某些情况下,这样也会带来其他麻烦。在本章稍后讲述sort
方法时,我将再次讨论这个问题。
2. count
count
方法统计某个元素在列表中出现的次数:
>>> ["to", "be", "or", "not", "to", "be"].count("to") 2
>>> x = [[1, 2], 1, 1, [2, 1, [1, 2]]] >>> x.count(1) 2
>>> x.count([1, 2]) 1
3. extend
extend
方法可以在列表的末尾一次性追加另一个序列中的多个值。换句话说,可以用新的列表扩展原有的列表:
>>> a = [1, 2, 3] >>> b = [4, 5, 6] >>> a.extend(b) >>> a
[1, 2, 3, 4, 5, 6]
这个操作看起来很像连接操作,两者最主要的区别在于:extend
方法修改了被扩展的序列(在这个例子中,就是a
)。而原始的连接操作则不然,它会返回一个全新的列表:
>>> a = [1, 2, 3] >>> b = [4, 5, 6] >>> a + b
[1, 2, 3, 4, 5, 6] >>> a
[1, 2, 3]
你可以看到连接的列表与之前例子中被扩展的列表是一样的,但是这一次它并没有被修改。这是因为原始的连接操作创建了一个包含a
和b
副本的新列表。如果需要如下例所示的操作,那么连接操作的效率会比extend
方法低。
>>> a = a + b
同样,这里也不是一个原位置操作,它并不会修改原来的列表。
我们可以使用分片赋值来实现相同的结果:
>>> a = [1, 2, 3] >>> b = [4, 5, 6] >>> a[len(a):] = b >>> a
[1, 2, 3, 4, 5, 6]
虽然这么做是可行的,但是代码的可读性就不如使用extend
方法了。
4. index
index
方法用于从列表中找出某个值第一个匹配项的索引位置:
>>> knights = ["We", "are", "the", "knights", "who", "say", "ni"] >>> knights.index("who") 4
>>> knights.index("herring")
Traceback (most recent call last):
File "<stdin>", line 1, in <module> ValueError: 'herring' is not in list
当搜索单词who
的时候,就会发现它在索引号为4
的位置。然而,当搜索"herring"
的时候,就会引发一个异常,因为这个单词没有被找到。
5. insert
insert
方法用于将对象插入列表中:
>>> numbers = [1, 2, 3, 5, 6, 7] >>> numbers.insert(3, "four") >>> numbers
[1, 2, 3, 'four', 5, 6, 7]
与extend
方法一样,insert
方法的操作也可以用分片赋值来实现。
>>> numbers = [1, 2, 3, 5, 6, 7] >>> numbers[3:3] = ["four"] >>> numbers
[1, 2, 3, 'four', 5, 6, 7]
这样做有点新奇,但是它的可读性绝对不如insert
方法。
6. pop
pop
方法会移除列表中的一个元素(默认是最后一个),并且返回该元素的值:
>>> x = [1, 2, 3] >>> x.pop() 3
>>> x
[1, 2] >>> x.pop(0) 1
>>> x
[2]
注:pop
方法是唯一一个既能修改列表又返回元素值(除了None
)的列表方法。
使用pop
方法可以实现一种常见的数据结构——栈。栈的原理就像堆放盘子那样。只能在顶部放盘子,同样,也只能从顶部拿走一个盘子。最后被放入栈堆的最先被移除(这个原则成为LIFO,即后进先出)。
对于上述的两个栈操作(放入和移出),它们有大家都认可的称谓——入栈(push
)和出栈(pop
)。Python没有入栈方法,但可以使用append
方法来代替。pop
方法和append
方法的操作结果恰好相反,如果入栈(或者追加)刚刚出栈的值,最后得到的结果还是原来的栈。
>>> x = [1, 2, 3] >>> x.append(x.pop()) >>> x
[1, 2, 3]
注:如果需要实现一个先进先出(FIFO)的队列(queue
),那么可以使用insert(0, ...)
来代替append
方法。或者,也可以继续使用append
方法,但必须用pop(0
)来代替pop()
。更好的解决方案是使用collection
模块中的deque
对象。要了解更详细的信息,请参见第十章
7. remove
remove
方法用于移除列表中某个值的第一个匹配项:
>>> x = ["to", "be", "or", "not", "to", "be"] >>> x.remove("be") >>> x
['to', 'or', 'not', 'to', 'be'] >>> x.remove("bee")
Traceback (most recent call last):
File "<stdin>", line 1, in <module> ValueError: list.remove(x): x not in list
可以看到:只有第一次出现的值被移除了,而不存在于列表中的值(比如例子中的"bee"
)是不会移除的。
值得注意的是,remove
是一个没有返回值的原位置改变的方法。它修改了列表却没有返回值,这与pop
方法相反。
8. reverse
reverse
方法将列表中的元素反向存放(我猜你们对此不会特别惊讶):
>>> x = [1, 2, 3] >>> x.reverse() >>> x
[3, 2, 1]
请注意,该方法也改变了列表但不反悔值(就像remove
和sort
)。
注:如果需要对一个序列进行反向迭代,那么可以使用reversed
函数。这个函数并不返回一个列表,而是返回一个迭代器(iterator
)对象(第九章介绍了更多关于迭代器的内容)。尽管如此,使用list
函数把返回值的对象转换成列表也是可行的:
>>> x = [4, 6, 2, 1, 7, 9] >>> list(reversed(x))
[9, 7, 1, 2, 6, 4]
9.sort
sort
方法用于在原位置(从Python2.3开始,sort
方法使用了固定的排序算法)对列表进行排序。在“原位置排序”意味着改变原来的列表,从而让其中的元素未能按一定的顺序排列,而不是简单地返回一个已排序的列表副本。
>>> x = [4, 6, 2, 1, 7, 9] >>> x.sort() >>> x
[1, 2, 4, 6, 7, 9]
前面介绍过了几个改变列表却不返回值的方法,在大多数情况下这样的行为方式是很合常理的(例如append
方法)。但是,sort
方法的这种行为方式需要重点讲解一下,因为很多人都被sort
方法弄糊涂了。当用户需要一个排好序的列表副本,同时又保留原有列表不变的时候,问题就出现了。为了实现这个功能,我们自然而然就想到了如下的做法(实际是错误的):
>>> x = [4, 6, 2, 1, 7, 9] >>> y = x.sort() # Don't do this!
>>> print y
None
因为sort
方法修改了x却返回了空值,那么最后得到的是已排序的x
以及值为None
的y
。实现这个功能的正确方法是,首先把x
的副本赋值给y
,然后对y
进行排序,如下例所示:
>>> x = [4, 6, 2, 1, 7, 9] >>> y = x[:] >>> y.sort() >>> x
[4, 6, 2, 1, 7, 9] >>> y
[1, 2, 4, 6, 7, 9]
再次调用x[:]
得到的是包含了x所有元素的分片,这是一种很有效率的赋值整个列表的方法。只是简单地把x
赋值给y
是没有用的,因为这样做就让x
和y
都指向同一个列表了。
>>> x = [4, 6, 2, 1, 7, 9] >>> y = x >>> y.sort() >>> x
[1, 2, 4, 6, 7, 9] >>> y
[1, 2, 4, 6, 7, 9]
另一种获取已排序的列表副本的方法是,使用sorted
函数:
>>> x = [4, 6, 2, 1, 7, 9] >>> y = sorted(x) >>> x
[4, 6, 2, 1, 7, 9] >>> y
[1, 2, 4, 6, 7, 9]
这个函数实际上可以用于任何序列,却总是返回一个列表(sorted
函数可以用于任何可迭代的对象。有关可迭代对象的详细内容,请参见第九章):
>>> sorted("Python")
['P', 'h', 'n', 'o', 't', 'y']
如果想把一些元素按相反的顺序排序,可以先使用sort
(或者sorted
),然后再调用reverse
方法(注意,需要分两次对列表调用sort
方法以及reverse
方法。如果打算通过x.sort().reverse()
来实现,会发现行不通,因为x.sort()
返回的是None
。当然,sorted(x).reverse()
是正确的做法),或者也可以使用reverse
参数,下一节将对此进行描述。
10.高级排序
如果希望元素能按照特定的方式进行排序(而不是sort
函数默认的方式,即根据Python的默认排序规则按升序排列元素,第五章内对此进行详解),那么可以通过compare(x, y)
的形式自定义比较函数。compare(x, y)
函数会在x < y
时返回负数,在 x > y
时返回正数,如果 x = y
则返回0(根据你的定义)。定义好该函数之后,就可以提供给sort
方法作为参数了。内建函数cmp
提供了比较函数的默认实现方式:
>>> cmp(42, 32) 1
>>> cmp(99, 100) -1
>>> cmp(19, 19)
0
>>> numbers = [5, 2, 9, 7]
>>> numbers.sort(cmp)
>>> numbers
[2, 5, 7, 9]
sort
方法有另外两个可选的参数——key
和reverse
。如果要使用它们,那么就要通过名字来指定(这叫做关键字参数,请参见第六章以了解更多的内容)。参数key
与参数cmp
类似——必须提供一个在排序过程中使用的函数。然而,该函数并不是直接用来确定对象的大小,而是为每个元素创建一个键,然后所有元素根据键来排序。因此,如果要根据元素的长度进行排序,那么可以使用len
作为键函数:
>>> x = ["aardvark", "abalone", "acme", "add", "aerate"] >>> x.sort(key=len) >>> x
['add', 'acme', 'aerate', 'abalone', 'aardvark']
另一个关键字参数reverse
是简单的布尔值(True
或者是False
。第五章会讲述更详细的内容),用来指明列表是否要进行反向排序。
>>> x = [4, 6, 2, 1, 7, 9] >>> x.sort(reverse=True) # True为反向排序
>>> x
[9, 7, 6, 4, 2, 1] >>> x = [4, 6, 2, 1, 7, 9]
>>> x.sort(reverse=False) # Flase为正向排序
>>> x
[1, 2, 4, 6, 7, 9]
cmp
、key
、reverse
参数都可以用于sorted
函数。在多数情况下,为cmp
或key
提供自定义函数是非常有用的。第六章将会讲述如何定义自己的函数。
注:如果想了解更多有关于排序的内容,可以查看Andrew Dalke的 “Sorting Mini-HOWTO”,链接是:http://wiki.python.org/moin/HowTo/Sorting 。
2.4 元组:不可变序列
元组与列表一样,也是一种序列。唯一的不同是元组不能修改(元组和列表在技术实现上有一些不同,但是在实际使用时,可能不会注意到。而且,元组没有像列表一样的方法。)。(你可能注意到了,字符串也是如此)创建元组的语法很简单:如果你用逗号分隔了一些值,那么你就自动创建了元组。
>>> 1, 2, 3 (1, 2, 3)
元组也是(大部分时候是)通过圆括号括起来的:
>>> (1, 2, 3)
(1, 2, 3)
空元组可以用没有包含内容的两个圆括号来表示:
>>> ()
()
那么如何实现包括一个值的元组呢。实现方法有些奇特——必须加个逗号,即使只有一个值:
>>> 42
42
>>> 42,
(42,) >>> (42,)
(42,)
最后两个例子生成了一个长度为1的元组,而第一个例子根本不是元组。逗号是很重要的,只添加圆括号也是没用的:(42)
和42
是完全一样的。但是,一个逗号却能彻底地改变表达式的值:
>>> 3 * (40 + 2) 126
>>> 3 * (40 + 2,)
(42, 42, 42)
2.4.1 tuple函数
tuple
函数的功能与list
函数基本上是一样的:以一个序列作为参数并把它转换为元组(tuple
并不是真正的函数——而是一种类型。在之前讲述list
函数的时候,我也提到了这一点。同时,与list
函数一样,目前也可以放心地忽略这一点)。如果参数就是元组,那么该参数就会被原样返回:
>>> tuple([1, 2, 3])
(1, 2, 3) >>> tuple("abc")
('a', 'b', 'c') >>> tuple((1, 2, 3))
(1, 2, 3)
2.4.2 基本元组操作
元组其实并不复杂——除了创建元组和访问元组元素之外,也没有太多其他的操作,可以参照其他类型的序列来实现:
>>> x = 1, 2, 3
>>> x[1] 2
>>> x[0:2]
(1, 2)
元组的分片还是元组,就像列表的分片还是列表一样。
2.4.3 那么,意义何在
现在你可能会想到底有谁会需要像元组那样的不可变序列呢?难道就不能在不改变其中内容的时候坚持只用列表吗?一般来说这是可行的。但是由于以下两个重要的原因,元组是不可替代的。
√ 元组可以在映射(和集合的成员)中当做键使用——而列表则不行(本章导言部分提到过映射,更多有关映射的内容,请参看第四章)。
√ 元组作为很多内建函数和方法的返回值存在,也就是说你必须对元组进行处理。只要不尝试修改元组,那么,“处理”元组在绝大多数情况下就是把它们当做列表来进行操作(除非需要使用一些元组没有的方法,例如index
和count
)。
一般来说,列表可能更能满足对序列的所有需求。
2.5 小结
让我们回顾本章所涵盖的一些最重要的内容。
√ 序列。序列是一种数据结构,它包含的元素都进行了编号(从0
开始)。典型的序列包括列表、字符串和元组。其中,列表是可变的(可以进行修改),而元组和字符串是不可变的(一旦创建了就是固定的)。通过分片操作可以访问序列的一部分,其中分片需要两个索引号来指出分片的起始和结束位置。要想改变列表,则要对相应的位置进行赋值,或者使用赋值语句重写整个分片。
√ 成员资格。in
操作符可以检查一个值是否存在于序列(或者其他的容器)中。对字符串使用in
操作符是一个特例,它可以查找子字符串。
√ 方法。一些内建类型(例如列表和字符串,元组则不在其中)具有很多有用的方法。这些方法有些像函数,不过它们与特定值联系得更密切。方法是面向对象编程的一个重要的概念,稍后的第七章中会对其进行讨论。
2.5.1 本章的新函数
cmp(x, y) 比较两个值
len(seq) 返回序列的长度
list(seq) 把序列转换成列表
max(args) 返回序列或者参数集合中的最大值
min(args) 返回序列或者参数集合中的最小值
reversed(seq) 对序列进行反向迭代
sorted(seq) 返回已排序的包含seq所有元素的列表
tuple(seq) 把序列转换成元组
2.5.2 接下来学什么
序列已经介绍完了,下一章会继续介绍由字符组成的序列,即字符串。