3.2 列表
前面已经用了很多次列表,可以看出列表的功能是比较强大的。本节将讨论列表不同于元组和字符串的地方:列表的内容是可变的(mutable)。列表有很多比较好用、比较独特的方法,本节将一一进行介绍。
3.2.1 更新列表
我们在3.1节中所讲述的有关序列的操作,如索引、分片、相加、乘法等都适用于列表。本节将介绍一些序列中没有而列表中有的方法,这些方法的作用都是更新列表,有元素赋值、增加元素、删除元素、分片赋值等。下面逐一进行介绍。
1. 元素赋值
我们前面学习过赋值语句,赋值语句是最简单的改变列表的方式,如a=2就属于一种改变列表的方式。这里我们将通过编号标记某个特定位置的元素,并对该位置的元素重新赋值,如a[1]=10。在交互模式下输入如下:
>>> a=[1,2,3,2,1] >>> a[1]=10 >>> a [1, 10, 3, 2, 1] >>> a[3]=10 >>> a [1, 10, 3, 10, 1]
还记得我们的编号是从0开始吧!
从上面的输出结果可以得知,我们可以根据编号对列表中某个元素重新赋值。既然可以重新赋值,是否可以赋不同类型的值呢?我们尝试一下,输入如下:
>>> a[2]='hello' #对编号为2的元素赋值,赋一个字符串 >>> a [1, 10, 'hello', 10, 1] >>> type(a) <class 'list'> >>> type(a[1]) #别忘了查看类型函数的使用 <class 'int'> >>> type(a[2]) <class 'str'>
由上面的输出结果可以得知,可以对一个列表中的元素赋不同类型的值。比如上面的示例,列表a中既有int类型的值,也有str类型的值。
假如对列表赋值时使用的编号超过了列表中的最大编号,是否还可以赋值呢?我们尝试一下,输入如下:
>>> tring=[1,2,3] >>> tring[3]='test' Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list assignment index out of range
在上面的示例中,tring的最大编号是2,当给编号为3的元素赋值时就会出错。由此得知:不能为一个不存在元素的位置赋值。如果一定要赋值,前面学习的序列通用操作中的乘法可以帮助我们,输入如下:
>>> tring=[None]*5 >>> tring[3]='test' >>> tring [None, None, None, 'test', None]
由以上输出结果可以得知,可以对初始化过的位置进行赋值。
提 示
不能为一个不存在元素的位置赋值。
2. 增加元素
由元素赋值的示例可以看到,不能为一个不存在的位置赋值。一旦初始化了一个列表,就不能再往这个列表中增加元素了。若需要往列表中增加元素,则需要将整个列表中的元素都复制一遍,再添加需要增加的元素。Python中是否提供对应的方法帮助我们做这件事情呢?答案是肯定的。输入如下:
>>> tring=[1,2,3] >>> tring.append(4) >>> tring [1, 2, 3, 4]
由示例看到,可以使用append()方法解决前面的困惑。append()方法是一个用于在列表末尾添加新对象的方法。该方法的语法如下:
list.append(obj)
此语法中list代表列表,obj代表需要添加到list列表末尾的对象。
提 示
append的使用方式是list.append(obj)。
由前面的输出结果得知:append()方法不是简单地返回一个修改过的新列表,而是直接修改原来的列表。下面展示几个append()的示例:
>>> tring=[1,2,3] >>> tring.append('test') #添加字符串 >>> tring [1, 2, 3, 'test'] >>> s=['a','b','c'] >>> s.append(3) #添加数字 >>> s ['a', 'b', 'c', 3]
由上面的示例可以得知:可以往数字序列中添加字符串,也可以往字符串序列中添加数字。
3. 删除元素
前面学习了往列表中增加元素,是否可以在列表中删除元素呢?例如下面的示例:
>>> tring=['a','b','c','d','e'] >>> len(tring) 5 >>> del tring[1] >>> print('删除第二个元素:',tring) 删除第二个元素: ['a', 'c', 'd', 'e'] >>> len(tring) 4
由上面的示例看到,可以使用del删除列表中的元素。上面的示例使用del删除了tring列表中的第二个元素,删除元素后,原来有5个元素的列表变成只有4个元素的列表了。使用del除了可以删除列表中的字符外,也可以删除列表中的数字,输入如下:
>>> num=[1,2,3] >>> len(num) 3 >>> del num[2] >>> print('删除第3个元素后:',num) 删除第3个元素后:[1, 2] >>> len(num) 2
上面的输出结果已经从数字列表中删除了对应的数字。
除了删除列表中的元素外,del还能用于删除其他元素,具体在后续章节会做详细介绍。
4. 分片赋值
分片赋值是列表一个强大的特性。先看下面的示例:
>>> list('女排夺冠了') ['女', '排', '夺', '冠', '了'] >>> boil=list('女排夺冠了') >>> boil ['女', '排', '夺', '冠', '了'] >>> show=list('hi,boy') >>> show ['h', 'i', ',', 'b', 'o', 'y'] >>> show[3:]=list('man') >>> show ['h', 'i', ',', 'm', 'a', 'n']
由上述示例可以看出,可以通过分片赋值直接对列表进行变更。示例中我们通过分片操作变更了编号3之后位置的元素,即将boy替换为man了。
上述示例中引入了一个新函数——list()函数。list()函数可以直接将字符串转换为列表。该函数的一个功能就是根据字符串创建列表,有时这么操作会很方便。list()函数不仅适用于字符串,所有类型的序列它都适用。
除了上面展示的功能,分片赋值还有什么强大的功能呢?先看下面的示例:
>>> greeting=list('hi') >>> greeting ['h', 'i'] >>> greeting[1:]=list('ello') >>> greeting ['h', 'e', 'l', 'l', 'o']
我们分析一下,前面给greeting赋的值是['h', 'i'],后面通过分片赋值操作将编号1之后的元素变更了,即将编号1位置的元素替换为e了,但是编号2之后没有元素,怎么能操作成功呢?并且一直操作到编号为4的位置,这怎么可以?
这就是分片赋值另一个强大的功能,可以使用与原序列不等长的序列将分片替换。
还有没有其他功能呢?请看下面的示例:
>>> field=list('ae') >>> field ['a', 'e'] >>> field [1:1]=list('bcd') >>> field ['a', 'b', 'c', 'd', 'e'] >>> boil=list('女排夺冠了') >>> boil ['女', '排', '夺', '冠', '了'] >>> boil[2:2]=list('2016年奥运会') >>> boil ['女', '排', '2', '0', '1', '6', '年', '奥', '运', '会', '夺', '冠', '了']
从上面的示例可以看出,可以在不替换任何原有元素的情况下在任意位置插入新元素。读者可自行尝试在上面示例的其他位置进行操作。
当然,上面的示例程序只是“替换”了一个空分片,实际操作是插入一个序列。
通过该示例是否想起了前面的append()方法,不过分片赋值比append()方法强大多了,append()方法只能在列表尾部增加元素,而分片赋值可以在任意位置增加元素。
看到这里,是否同时想起了前面删除元素的操作,分片赋值是否支持类似删除的功能呢?是的,支持类似删除的功能。下面我们证实一下这个猜想。
>>> field=list('abcde') >>> field ['a', 'b', 'c', 'd', 'e'] >>> field[1:4]=[] >>> field ['a', 'e'] >>> boil=list('女排2016年奥运会夺冠了') >>> boil ['女', '排', '2', '0', '1', '6', '年', '奥', '运', '会', '夺', '冠', '了'] >>> boil[2:10]=[] >>> boil ['女', '排', '夺', '冠', '了'] >>> del field[1:4] >>> field ['a', 'e']
从上面的示例可以看到,使用了前面插入操作的逆操作证实我们的猜想。删除和插入一样,可以对一个序列中任意位置的元素进行删除。
所以通过分片赋值删除元素也是可行的,并且分片赋值删除的功能和del删除的操作结果是一样的。
3.2.2 嵌套列表
前面介绍的都是单层的列表,列表是否可以嵌套呢?我们做如下尝试:
>>> field=['a','b','c'] >>> field ['a', 'b', 'c'] >>> num=[1,2,3] >>> num [1, 2, 3] >>> mix=[field,num] >>> mix [['a', 'b', 'c'], [1, 2, 3]] >>> mix[0] ['a', 'b', 'c'] >>> mix[1] [1, 2, 3]
由上面的操作结果得知,在列表中可以嵌套列表,嵌套的列表取出后还是列表。
3.2.3 列表方法
之前的章节介绍了函数,本节介绍一个与函数密切相关的概念——方法。
方法是与对象有紧密联系的函数,对象可能是列表、数字,也可能是字符串或其他类型的对象。方法的调用方式前面有一个示例,调用语法如下:
对象.方法(参数)
由上面的语法和前面append()方法的示例可知:方法的定义方式是将对象放到方法名之前,两者之间用一个点号隔开,方法后面的括号中可以根据需要带上参数。除了语法上有一些不同外,方法调用和函数调用很相似。
列表中有count、index、sort等比较常用的方法,下面逐一进行介绍。
1. append
该方法前面已经介绍过,功能是在列表的末尾添加新对象。
使用方式为:
list.append(obj)
2. count
count()方法用于统计某个元素在列表中出现的次数。
count()方法的语法如下:
list.count(obj)
此语法中,list代表列表,obj代表列表中统计的对象。
使用该方法的示例如下:
>>> field=list('hello,world') >>> field ['h', 'e', 'l', 'l', 'o', ',', 'w', 'o', 'r', 'l', 'd'] >>> print('列表field中,字母o的个数:',field.count('o')) #统计列表中的字符个数 列表field中,字母o的个数: 2 >>> print('列表field中,字母l的个数:',field.count('l')) 列表field中,字母l的个数: 3 >>> print('列表field中,字母a的个数:',field.count('a')) 列表field中,字母a的个数: 0 >>> listobj = [123, 'hello', 'world', 123] >>> listobj = [26, 'hello', 'world', 26] >>> print('数字26 的个数:',listobj.count(26)) 数字26 的个数: 2 >>> print('hello的个数:',listobj.count('hello'))#统计字符串个数 hello的个数: 1 >>> ['a','c','a','f','a'].count('a') 3 >>> mix=[[1,3],5,6,[1,3],2,] >>> print('嵌套列表mix中列表[1,3]的个数为:',mix.count([1,3])) 嵌套列表mix中列表[1,3]的个数为: 2
3. extend
extend()方法用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)。
extend()方法的语法如下:
list.extend(seq)
此语法中,list代表被扩展的列表,seq代表需要追加到list中的元素列表。
使用该方法的示例如下:
>>> a=['hello','world'] >>> b=['python','is','funny'] >>> a.extend(b) >>> a ['hello', 'world', 'python', 'is', 'funny']
以上操作结果看起来很像连接操作。extend()方法和序列相加有什么区别呢?我们先看看下面的示例:
>>> a=['hello','world'] >>> b=['python','is','funny'] >>> a+b ['hello', 'world', 'python', 'is', 'funny'] >>> a ['hello', 'world']
从输出的结果可以看出,两个示例中a和b的赋值是一样的,但第一个示例中输出a的值和第二个示例中输出a的值不一样。
由此我们得出,extend()方法和序列相加的主要区别是:extend()方法修改了被扩展的序列,如前面的a;原始的连接操作会返回一个全新的列表,如上面的示例,返回的是一个包含a和b副本的新列表,而不会修改原始的变量。
当然,上述示例也可以用前面学习的分片赋值实现相同的结果,输入如下:
>>> a=['hello','world'] >>> b=['python','is','funny'] >>> a[len(a):]=b >>> a ['hello', 'world', 'python', 'is', 'funny']
可以看到,输出结果和使用extend()方法一样,不过看起来没有extend()方法易懂,因此不会选择这个方案。
4. index
index()方法用于从列表中找出某个值第一个匹配项的索引位置。
index()方法的语法如下:
list.index(obj)
此语法中,list代表列表,obj代表查找的对象。
使用该方法的示例如下:
>>> field=['hello', 'world', 'python', 'is', 'funny'] >>> print('hello的索引位置为:',field.index('hello')) hello的索引位置为: 0 >>> print('python的索引位置为:',field.index('python')) python的索引位置为: 2 >>> print('abc的索引位置为:',field.index('abc')) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: 'abc' is not in list
由上面的示例看到,搜索单词hello,会发现它在索引号为0的位置;搜索单词python,它在索引号为2的位置,索引得到的位置跟元素在序列中的位置一样。如果搜索列表中不存在的字符串,操作结果就会出错,所以对于不在列表中的元素,用index()方法操作时会报错。
5. insert
insert()方法用于将对象插入列表。
insert()方法的语法如下:
list.insert(index,obj)
此语法中,list代表列表,index代表对象obj需要插入的索引位置,obj代表要插入列表中的对象。
使用该方法的示例如下:
>>> num=[1,2,3] >>> print('插入之前的num:',num) 插入之前的num: [1, 2, 3] >>> num.insert(2,'插入位置在2之后,3之前') >>> print('插入之后的num:',num) 插入之后的num: [1, 2, '插入位置在2之后,3之前', 3]
由上面的示例看到,insert()方法操作挺方便的。
与extend()方法一样,insert()方法的操作也可以使用我们前面学习的分片赋值实现。
>>> num=[1,2,3] >>> print('插入之前的num:',num) 插入之前的num: [1, 2, 3] >>> num[2:2]=['插入位置在2之后,3之前'] >>> print('插入之后的num:',num) 插入之后的num: [1, 2, '插入位置在2之后,3之前', 3]
输出结果和insert操作的结果一样,但看起来没有使用insert容易理解,应该不会选择这个方案。
6. pop
pop()方法用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
pop()方法的语法如下:
list.pop(obj=list[-1])
此语法中,list代表列表,obj为可选择的参数,代表要移除列表元素的对象。
使用该方法的示例如下:
>>> field=['hello', 'world', 'python', 'is', 'funny'] >>> field.pop() #不传参数,默认移除最后一个元素 'funny' >>> print('移除元素后的field:',field) 移除元素后的field: ['hello', 'world', 'python', 'is'] >>> field.pop(3) #移除编号为3的元素 'is' >>> print('移除元素后的field:',field) 移除元素后的field: ['hello', 'world', 'python'] >>> field.pop(0) 'hello' >>> print('移除元素后的field:',field) 移除元素后的field: ['world', 'python']
由上面的示例看到,调用pop方法移除元素时,在交互模式下会告知我们移除了哪个元素,如上面示例中的funny、is。移除funny时未传参数,默认移除最后一个;is的移除则是根据传入的编号3进行的。
提 示
pop方法是唯一一个既能修改列表又能返回元素值(除了None外)的列表方法。
使用pop方法可以实现一种常见的数据结构——栈。
栈的原理就像堆放盘子一样,一次操作一个盘子,要将若干盘子堆成一堆,只能在一个盘子的上面放另一个盘子;要拿盘子时,只能从顶部一个一个往下拿,最后放入的盘子是最先被拿的。栈也是这样,最后放入栈的最先被移除,称为LIFO(Last In First Out),即后进先出。
栈中的放入和移除操作有统一的称谓——入栈(push)和出栈(pop)。Python没有入栈方法,但可以使用append方法代替。pop方法和append方法的操作结果恰好相反,如果入栈(或追加)刚刚出栈的值,最后得到的结果就不会变,例如以下操作:
>>> num=[1,2,3] >>> num.append(num.pop()) #追加默认出栈的值 >>> print('num追加默认出栈值的操作结果:',num) num追加默认出栈值的操作结果: [1, 2, 3]
由上面的操作结果看到,通过追加默认出栈的值得到的列表和原来是一样的。
7. remove
remove()方法用于移除列表中某个值的第一个匹配项。
remove()方法的语法如下:
list.remove(obj)
此语法中,list代表列表,obj为列表中要移除的对象。
使用该方法的示例如下:
>>> field=['女排','精神','中国','精神','学习','精神'] >>> print('移除前列表field:',field) 移除前列表field: ['女排', '精神', '中国', '精神', '学习', '精神'] >>> field.remove('精神') >>> print('移除后列表field:',field) 移除后列表field: ['女排', '中国', '精神', '学习', '精神'] >>> field.remove('abc') #删除列表中不存在的元素 Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.remove(x): x not in list
由上面的输出结果看到,只有第一次出现的值被移除了,第二次之后出现的值没有被移除。上面的列表中有3个“精神”,调用移除方法后,删除了第一个,后面两个仍然存在。
同时,操作移除列表中不存在的值是不行的,系统会告知移除的对象不在列表中。
有一点需要了解的是:remove没有返回值,是一个直接对元素所在位置变更的方法,它修改了列表却没有返回值,与pop方法正好相反。
8. reverse
reverse()方法用于反向列表中的元素。
reverse()方法的语法如下:
list.reverse()
此语法中,list代表列表,该方法不需要传入参数。
使用该方法的示例如下:
>>> num=[1,2,3] >>> print('列表反转前num:',num) 列表反转前num: [1, 2, 3] >>> num.reverse() >>> print('列表反转后:',num) 列表反转后: [3, 2, 1]
由上面的输出结果看到,该方法改变了列表但不返回值(和前面的remove一样)。
提 示
如果需要对一个序列进行反向迭代,那么可以使用reversed函数。这个函数并不返回列表,而是返回一个迭代器(Iterator)对象(该对象在后面会详细介绍),可以通过list函数把返回的对象转换为列表,例如:
>>> num=[1,2,3] >>> print('使用reversed函数翻转结果:',list(reversed(num))) 使用reversed函数翻转结果: [3, 2, 1]
输出结果对原序列反向迭代了。
9. sort
sort()方法用于对原列表进行排序,如果指定参数,就使用参数指定的比较方法进行排序。
sort()方法的语法如下:
list.sort(func)
此语法中,list代表列表,func为可选参数。如果指定该参数,就会使用该参数的方法进行排序。
使用该方法的示例如下:
>>> num=[5,8,1,3,6] >>> num.sort() >>> print('num调用sort方法后:',num) num调用sort方法后: [1, 3, 5, 6, 8]
由上面输出的结果得知,sort方法改变了原来的列表,而不是简单地返回一个已排序的列表副本。
我们前面学习过几个改变列表却不返回值的方法(如append),不能将操作结果赋给一个变量,这样的行为方式很合常理。但当用户需要一个排好序的列表副本,同时又保留原有列表不变时,可能会做如下操作:
>>> num=[5,8,1,3,6] >>> n=num.sort() >>> print('变量n的结果是:',n) 变量n的结果是: None >>> print('列表num排序后的结果是:',num) 列表num排序后的结果是: [1, 3, 5, 6, 8]
输出结果怎么和我们预期的不一样呢?因为sort方法修改了列表num,但是返回的是空值,所以我们最后得到的是已排序的num和值为None的n。该想法正确的实现方式是先把num的副本赋值给n,然后对n进行排序,操作如下:
>>> num=[5,8,1,3,6] >>> n=num #直接将列表num赋值给n >>> n.sort() >>> print('变量n的结果是:',n) 变量n的结果是: [1, 3, 5, 6, 8] >>> print('num的结果是:',num) #num也被排序了 num的结果是: [1, 3, 5, 6, 8] >>> num=[5,8,1,3,6] >>> n=num[:] #将列表num切片后赋值给n >>> n.sort() >>> print('变量n的结果是:',n) 变量n的结果是: [1, 3, 5, 6, 8] >>> print('num的结果是:',num) #num保持原样 num的结果是: [5, 8, 1, 3, 6]
由上面的执行结果可以看到,若不将原列表(如列表num)分片后赋值给另一个变量(如n),则两个列表都会被排序,这样简单的赋值后,两个列表都指向同一个列表。由此提醒进行该操作时要记得对原列表分片。
如reverse方法一样,sort方法也有一个有同样功能的函数——sorted函数。该函数可以直接获取列表的副本进行排序,使用方式如下:
>>> num=[5,8,1,3,6] >>> n=sorted(num) >>> print('变量n的操作结果是:',n) 变量n的操作结果是: [1, 3, 5, 6, 8] >>> print('num的结果是:',num) #num保持原样 num的结果是: [5, 8, 1, 3, 6]
执行结果和前面操作的一样。sorted函数可以用于任何序列,返回结果都是一个列表。例如下面的操作:
>>> sorted('python') ['h', 'n', 'o', 'p', 't', 'y'] >>> sorted('321') ['1', '2', '3']
10. clear
clear()方法用于清空列表,类似于del a[:]。
clear()方法的语法如下:
list.clear()
此语法中,list代表列表,不需要传入参数。
使用该方法的示例如下:
>>> field=['study','python','is','happy'] >>> field.clear() >>> print('field调用clear方法后的结果:',field) field调用clear方法后的结果: []
由操作结果看到,clear方法会清空整个列表,调用该方法进行清空很简单,但也要小心,因为一不小心就可能把整个列表都清空了。
11. copy
copy()方法用于复制列表,类似于a[:]。
copy()方法的语法如下:
list.copy()
此语法中,list代表列表,不需要传入参数。
使用该方法的示例如下:
>>> field=['study','python','is','happy'] >>> copyfield=field.copy() >>> print('复制操作结果:',copyfield) 复制操作结果: ['study', 'python', 'is', 'happy']
操作结果和该方法的意思一样,是原原本本的复制操作。
12. 高级排序
如果希望元素按特定方式进行排序(不是sort方法默认的按升序排列元素),就可以自定义比较方法。
sort方法有两个可选参数,即key和reverse。要使用它们,就要通过名字指定,我们称之为关键字参数。例如下面的示例:
>>> field=['study','python','is','happy'] >>> field.sort(key=len) #按字符串由短到长排序 >>> field >>> field.sort(key=len,reverse=True) #按字符串由长到短排序,传递两个参数 >>> field ['python', 'study', 'happy', 'is'] ['is', 'study', 'happy', 'python'] >>> num=[5,8,1,3,6] >>> num.sort(reverse=True) #排序后逆序 >>> num [8, 6, 5, 3, 1]
由上面的操作结果可知,sort方法带上参数后的操作是很灵活的,可以根据自己的需要灵活使用该方法。关于自定义函数,后续章节会有更详细的介绍。