3.4 列表
列表(list)是Python的一个内置对象(或者对象类型),它具有强大的功能。在开始学习之前,先牢记一句顺口溜:列表是个筐,什么都能装。然后由浅入深、按部就班地研习这个“筐”的作用。
3.4.1 创建列表
Python中的列表可以用[]表示,它不像数字、字符串那样直接与自然语言中的内容对应,而是完全人为定义的对象类型。
用list表示列表对象,注意在上述操作中,使用的是lst这个变量引用了一个空列表对象,变量名称没有使用list,因为list 用来表示了列表对象(或列表对象类型)——变量的名称要尽可能不与类型名称重复。
定义空列表的方法还可以是这样的:
其实,对于数字和字符串而言,也可以有“空”数字、“空”字符串,虽然在自然语言中不需要这样,但在Python这种人工语言中是符合语法规则的。
列表也是一个容器——“一个筐”,这个容器中可以放的东西称为列表的元素。列表的元素可以是任何类型的Python对象——“什么都能装”。
在a_lst这个列表中,其元素包括数字、字符串、空列表和非空列表。其实还可以包括后续要学习的任何Python对象,也包括自定义的对象类型(函数、类等)。随着学习的深入,读者会逐渐理解。
有一种“列表套列表”的情况:
类似这样的列表被称为“多维列表”(此处是二维),类似数学中的矩阵。当然,可以做更多层的嵌套。
而且,列表中的元素可以重复,列表中的元素也对位置敏感。lst1与lst2相比,元素1和2的排序变化了,它们是两个不同列表。这与字符串类似,列表和字符串一样,都是“序列”。通过dir(list)查看列表的属性和方法,与字符串的属性和方法对比,会发现它们有相同的方法,如index(),而且作用一样。也就是说,列表同样有索引,可以进行切片操作。
【例题3-4-1】 有一种矩阵被称为单位矩阵,它是个方阵,从左上角到右下角的对角线上的元素均为1,除此以外全都为0。用列表表示一个3×3的单位矩阵。
代码示例
二维列表可以用来表示矩阵,第一个元素表示矩阵的第一行,以此类推。
3.4.2 索引和切片
列表既然是序列,也就跟字符串一样,其中的每个元素都有索引,而且索引的建立方式与字符串中所学习过的也一样。读者可以在交互环境中调试下面的各项操作,并回忆字符串中索引的特点。
如果从左边开始对各元素的索引编号,也是从0开始计数;如果从右边开始编号,也是从-1开始计数。这些都与字符串中的索引方式相同。
对列表进行切片的基本方法与字符串中的方法也是一致的。字符串中表示切片的公式在列表中依然适用,只不过对象换成列表罢了。
请读者进入到Python交互模式,按照下面的示例,练习列表的切片操作。
经过了这些切片操作后,再查看原来的列表:
原来的列表并没有因为上述操作而变化,这说明每次切片都是新建了对象,不对原列表进行修改。这种特点与字符串依然相同。
以上演示的是“一维列表”的索引和切片操作,在3.4.1节中定义过一个“二维列表”,那么它里面的元素如何获取?
就索引和切片的基本方法,列表和字符串没有区别。但列表也有独到之处。
①是通过索引修改元素对象,原来lst[0]是字符串(“a”),经过①之后,该位置变成了新的对象。这种操作在字符串中是不能进行的。
这显示了列表和字符串的最大区别:列表创建后,可以进行修改,而字符串不能修改。或者说,列表和字符串都是序列,它们有相同的地方;但列表和字符串又是两种类型的对象,它们必然存在不同。
【例题3-4-2】 有列表["a","b","c","d","e","f","g","h"],将列表翻转,并把其中的元音字母“e”转换为100。
代码示例
3.4.3 列表的基本操作
在3.3.3 节中所述的各项操作是所有序列都具有的,列表也是一种序列,所以同样可以实施其中的各项操作。
注意,以上操作都是新生成了一个列表,并没有对原列表进行修改。
基本操作与字符串中的都一样。两者不同的地方在于前面提到的,列表是可变的,而字符串是不可变的。这点不同使得列表具有一些不同于字符串的方法。
3.4.4 列表的方法
在3.4.2节中提到过,可以根据索引修改列表的元素。比如:
这种操作可以看作列表可修改的例证。那么,能不能给列表随时增加新的元素呢?比如对cities这个列表,是否可以用增加索引的方式增加元素?
操作结果说明,这种想法在这里是无法实现的。
对于任何可修改对象,一般都有“增”“删”“改”的操作,“改”这种操作在前面已经实现了,其他两项如何实现呢?
查看列表有哪些方法?还是使用前面已经多次提到过的dir函数。
在Python中,命名都是本着“望文生义”的原则。所以,读者认真看一看列出来的各名称,也能猜测到其大概功能。
1.增加列表的元素
与增加列表元素有关的方法包括append()、extend()、insert(),下面依次演示它们的使用方法。还是先老生常谈,读者一定要使用help函数查看并阅读各方法的联机帮助文档。
这是已知列表cities的内存地址,append()方法是对列表从尾部(通常以列表的左端为开始,右端为尾部)追加一个元素。
追加元素后,请仔细观察这时候内存地址的变化——没变化。当给列表追加了元素后,列表的内存地址没有改变,也就说明没有生成新的列表,cities还是原来的列表。操作②还有一个特点也要引起关注,那就是没有返回值,或者说返回值为None。
一个列表对象,其元素变化了,但列表对象的内存地址没有改变,也就是没有生成新的列表,这种现象可以被称为“原地修改”。如果把列表看作一个容器,可以形象地理解为容器内的东西(object,对象)变化了,但是容器还是原来的容器。
列表的这种特性在其他方法中也有体现。
列表的insert()方法实现了在列表任何位置插入对象的操作,依然是原地修改。例如:
在cities列表中,最大索引是3,如果进行如下操作:
试图将"ningbo"插入到索引是4的前面,但是没有这个索引。换个说法,4前面就是3的后面,最终效果与追加相同。
如果读者已经查看了append()和insert()两个方法的帮助文档,会发现它们向列表中增加的都是对象(object)——如果还没有查看,请马上动手。
下面要介绍的extend()方法,其参数要求传入的对象必须是iterable——可迭代对象。前面章节中已经提到过这类对象了,到目前为止,字符串和列表都是可迭代的。
③的效果是将列表lst中的所有元素加入到cities中,即让cities扩容。④将参数换成了另一个可迭代对象——字符串"py"。注意查看最终的效果,将字符串的元素(即每个字符)加入到cities列表中。
学程序一定要有好奇心,交互模式就是一个实验室,可以在这个环境中快速检验自己的想法,哪怕是比较愚蠢的想法。
“失败是成功之母”,每次遇到报错信息,都要认真阅读,就能不断积累经验。通过阅读此处的报错信息,读者应该更加认识到传入extend()方法的对象必须是可迭代的。
那么,如何判断一个对象是不是可迭代的?下面演示一种方法(事实上还有其他方式):
这里用内建函数hasattr判断一个字符串是否是可迭代的,返回了True。用同样的方式可以判断:
hasattr函数的判断本质就是看类型中是否有__iter__()这个特殊方法。读者可以用dir函数找一找,在数字、字符串、列表中,谁有__iter__()。
在③和④的操作中,对extend()方法提供的对象要求是“可迭代的”。除了这点,操作结果似乎与append()方法的结果一样。
看下面的操作,进行深入比较。
认真观察③和⑤的操作结果,可以理解append()与extend()方法的区别。
2.删除列表的元素
列表提供的删除元素的方法有两个:remove()和pop()。帮助文档显示,两个方法的调用方式分别是L.remove(x)、L.pop([index])。从参数中可以看出,这两个方法分别提供了依据元素(x)和依据索引(index)进行删除的方式。
请读者自行考察,经过上述操作之后,列表cities是否为原地修改。
如果某个元素不存在于列表中,进行此操作是要报错的。如何避免这个错误?最好提前进行判断:要删除的元素是否在列表中。
接着看另一个能够删除列表元素的方法——L.pop([index]),以[index]形式表示索引是可选的。
这里没有提供任何参数,即pop()方法的参数列表为空,则删除列表的最后一个元素,并且将删除的元素作为结果返回。注意,它有返回值。
如果参数不为空,可以删除指定索引的元素,并将该元素作为返回值。
对这两个删除方法简要总结如下:
❖ L.remove(x)中的参数x是列表中的元素,即删除某个元素,且对列表原地修改,无返回值。
❖ L.pop([index])中的index是列表中元素的索引值,可选。“为空”则删除列表最后一个,否则删除索引为index的元素,并且将删除元素作为返回值。
除了remove()和pop(),列表中还有一个方法,名为clear(),它的作用是将列表中元素“清扫干净”,只剩下一个空列表。
认真观察上述操作,执行了列表的clear()方法后,当前列表变成了空列表。但是列表的内存地址没有改变,“容器”中没有任何东西,“容器”并没有因此变化,还是原来的“容器”。
最终结果与前述操作结果看似一样,都是最终得到了一个空列表。实质上,这里的temp变量先后引用了两个不同的对象,对此处操作的理解就需要读者使用3.2.4节中学习过的“变量”与“对象”关系的知识了。
3.列表元素的排序
字符串是序列,但是对组成它的字符进行排序,通常实际意义不是很大;列表则不然,组成它的元素固然有某个顺序了,但是在实际应用中常常要将其按照某种顺序重新排列。比如,由若干人的姓名组成的列表,通常需要将列表按照人名的某种顺序排列(常见的按照字典中的拼音顺序排列)。更何况,如果非要对字符串中的字符排序,可以把它转化为列表。因为列表是可变的,这个特点为实现其排序提供了便利条件。
本着“望文生义”的原则,在列表的方法中可以看到sort(),就是用来对列表进行排序的方法。
sort()方法的结果是让列表原地修改,没有返回值。在默认情况下,如上面的操作,实现的是从小到大的排序。
在sort()中使用参数reverse=True,就实现了从大到小的排序。
如果读者查看sort()方法的文档,会发现它的完整格式中还有一个参数key。
key是什么意思呢?请看如下操作。
这样实现了以字符串的长度为关键词进行排序。
对于排序而言,Python中还提供了一个内置函数sorted。下面比较它与列表的方法sort()。
这里使用内置函数sorted对列表lst中的元素进行排序,因为这个列表中的元素是字符串,所以是按照字典顺序排序的。从结果上看,用sorted函数排序后,得到了一个新的列表对象,原列表没有变化。
如果使用列表的sort()方法进行排序,虽然结果一样,但是列表lst被原地修改了。这是两种排序方法的最大区别。
4.列表元素的反转
在3.4.2节中已经有方法实现列表元素的反转了。
除了这种反转方法,列表还提供了一种反转方法reverse,其调用方式比较简单。
注意,依然是原地修改,它没有返回值。
Python中也为反转操作提供了内建函数reversed,两者效果相当,但是也有区别。
使用reversed函数对列表a进行反转,得到了一个新的对象,这个对象是一个“迭代器”对象,所以不能像列表那样直观地看到里面的内容。可以使用list函数将此对象转换为列表。
其实,内置函数reversed的参数不仅可以是列表,还可以是任何其他序列,包括字符串。
以上介绍了列表中的常用方法,未尽内容,请读者参考官方文档。
【例题3-4-3】 将字符串"python"转化为列表(记为lst),并将“rust”中的每个字符作为一个独立元素追加到列表lst中,然后将重复的元素全部删除。
代码示例