根据已经有的经验,列表作为 Python 的内置对象,如同字符串那样,也会有自己的属性和方法。在交互模式中,执行 dir(list) 之后显示:
>>> dir(list)
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
仍然暂且不讨论双下划线开始和结尾的特殊方法和属性,只看下面这几个:
'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'
因为列表在 Python 语言中的特殊地位,所以要按照一定的类别,对这些方法的含义和调用逐个介绍。
首先回顾4.3.1节提到的修改列表成员的方法:
>>> university = ['Soochow', 'Yenching']
>>> university[1] = "Nanking"
>>> university
['Soochow', 'Nanking']
通过列表的索引,能够修改相应的成员,这说明列表是可修改的——字符串则不可修改。
那么,能不能通过给列表增加索引的方式增加新的成员呢?比如 university 这个列表,最大索引值是 1 ,用下面的方法,再增加一个索引,并同时增加一个成员:
>>> university[2] = 'Hangchow'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range
类似的错误信息在4.2.4节也看到过,不允许使用超过范围的索引。
列表的成员还能增加吗?能,必须用列表的方法。
1. 增加成员
与增加列表成员有关的方法是 append() 、extend() 、insert() ,这三个方法各有各的用途,读者从其名称上也能了解一二,不过要想知道它们的调用方法,须用 help() 查看帮助文档——此处不再演示查看结果,请读者在学习过程中自行查看。
首先记录列表 university 的内存地址,留待后用。
>>> university
['Soochow', 'Nanking']
>>> id(university)
140686307594496
然后,使用 append() 方法对列表从尾部(通常以列表的左端为开始,右端为尾部)追加一个成员。
>>> university.append('Hangchow') # (3)
>>> university
['Soochow', 'Nanking', 'Hangchow']
>>> id(university)
140686307594496
注释(3)就是 append() 的调用方法,如此即向 university 列表中追加了一个字符串 'Hangchow' 。
之后,请仔细观察列表 university 的内存地址——没变化。当给列表追加了元素之后,列表的内存地址没有改变,也就说明没有生成新的列表,university 还是原来的列表。如果通俗地按照本节开头“码谚”类比,“列表是个筐”,现在向“筐”里增加了东西,“筐”没有变,还是原来那个“筐”——很符合常识。
再观察注释(3),当执行了该语句之后,它没有返回值——这个现象不同以往,以往我们的经验是,执行了某条语句或表达式之后,会有返回结果,此处却没有。或者说,返回值为None ,这个值必须用 print() 函数才能显示出来。
>>> result = university.append("Lingnan") # (4)
>>> result # (5)
>>> print(result)
None
注释(4),将 university.append("Lingnan") 的返回值用变量 result 引用,但是在注释(5)中无法显示该值,必须用 print() 函数才能显示它是 None 。所以,如果在程序中使用 append() 或者与之在返回值上有相同情况的方法,切记不要使用注释(4)形式——切记,初学者容易犯的错误,而且事到临头之时自己还浑然不知,因为不报错。
像列表的 append() 方法这样,能够令列表中的成员变化,但列表对象的内存地址没有改变,也就是没有生成新的列表,称这种现象为列表原地修改——“筐”没变,变得是里面的东西。这也是列表作为可修改对象的特征表现,即里面的成员可修改。
再来看 insert() 方法,也能实现列表原地修改。
>>> university
['Soochow', 'Nanking', 'Hangchow', 'Lingnan']
>>> university.insert(1, 'Shanghai') # (6)
>>> id(university)
140686307594496
>>> university
['Soochow', 'Shanghai', 'Nanking', 'Hangchow', 'Lingnan']
注释(6)中 insert() 的第一个参数 1 是 university 的索引,第二个参数 'Shanghai' 为插入到索引位置之前的对象。执行之后,也是对列表原地修改——没有返回值是表象,本质是其内存地址未变。
>>> university.insert(5, [3, 5])
>>> university
['Soochow', 'Shanghai', 'Nanking', 'Hangchow', 'Lingnan', [3, 5]]
仔细观察上面的操作,是否能发现与之前经验貌似相悖之处?在 insert() 参数中,5 是 university 列表的索引吗?不是!执行了注释(6)之后,列表 university 的索引最大值是 4 ,索引 5 已经超过了其范围,然而没有如以前那样报错,非常稳妥地将第二个参数插入到了列表中,只是放到了最后一个。对此,可以理解为:既然 5 已经超过了索引范围,那么它“前面”的位置就应该在列表的“最后”,于是乎将对象插入到“最后”,这与追加方法 append() 等效。
不知道至此读者是否已经查看过列表的 append() 和 insert() 两个方法的帮助文档了,如果仍然未动手查看,请务必暂停,待查看之后再学——不养成好习惯,不提升能力,唯有叹息“早生华发,人生如梦”,“只是当时已惘然”。更何况,下面就要用到。
列表的 extend() 方法虽然在效果上能够增加列表中的成员,但其内涵与 append() 和 insert() 不同。extend() 方法的参数要求传入的对象必须是 iterable——可迭代对象(参阅第9章9.6节)。到目前为止仅知道字符串和列表是可迭代对象,所以,现在只能用这两个对象作为 extend() 的参数(注意,是“现在只能”,以后学习了其他可迭代对象,也能用于此方法)。
>>> lst = [1, 2, 3]
>>> ex_lst = [3.14, 7.28]
>>> lst.extend(ex_lst) # (7)
>>> lst
[1, 2, 3, 3.14, 7.28]
>>> ex_lst
[3.14, 7.28]
注释(7)以列表 ex_lst 作为 extend() 的参数,将其中的成员分别添加到列表 lst 中,或者说,用 ex_lst 的成员“扩充”( extend )了 lst 的成员——还是列表原地修改,并且原列表 ex_lst 也没有变化。
如果用字符串作为参数,结果如何?
>>> lst.extend('py')
>>> lst
[1, 2, 3, 3.14, 7.28, 'p', 'y']
由于字符串是以字符为成员,于是就将 'py' 中的两个字符作为两个成员依次用于“扩充”列表 lst 。
列表的 extend() 方法不难理解和使用,但它的参数必须是可迭代对象。目前我们知道的可迭代对象只有字符串和列表这两个,还算好记忆,如果将来遇到的对象多了,怎么知道某个对象是不是可迭代的?有没有判断方法?必须得有!下面演示一种方法(事实上还有其他方式,参阅第9章9.6节):
>>> s = 'python'
>>> hasattr(s, '__iter__') # 注意双下划线
True
Python 内置函数 hasattr() 可以判断对象是否具有某属性或方法,此处用它来判断字符串是否有 __iter__ ,返回了 True ,则说明字符串对象是可迭代的。也就是说,__inter__ 是一个对象是否可迭代的标志(参阅第9章9.6节)
用同样的方式可以判断:
>>> hasattr([1, 2], '__iter__')
True
>>> hasattr(123, '__iter__')
False
至此,已经了解了增加列表成员的三个方法。如果读者学有余力,不妨再认真比较 append() 和 extend() 两个方法有何区别。从比较中会初步体会到参数 object 和 interable 的差异。
2. 删除成员
列表删除成员的方法有三个, remove() 和 pop() 用于删除某个成员,clear() 用于清除所有成员。
>>> university # 承接之前的操作
['Soochow', 'Shanghai', 'Nanking', 'Hangchow', 'Lingnan', [3, 5]]
>>> university.remove([3, 5]) # (8)
>>> university
['Soochow', 'Shanghai', 'Nanking', 'Hangchow', 'Lingnan']
remove() 的参数是列表中的成员对象,即删除该成员,注释(8)将列表 university 中原有的成员 [3, 5] 给予删除,且列表原地修改——如何确认是原地修改?请参照前述内容。
如果 remove() 的参数不是列表的成员,会抛出异常信息。
>>> university.remove([3, 5])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
所以,最好是提前进行判断:要删除的元素是否在列表中。
>>> 'Shanghai' in university
True
>>> university.remove('Shanghai')
>>> university
['Soochow', 'Nanking', 'Hangchow', 'Lingnan']
另外一个能够删除列表成员的方法:L.pop([index]),以 [index] 形式表示参数,其意是以索引为参数,且可选。
>>> university
['Soochow', 'Nanking', 'Hangchow', 'Lingnan']
>>> university.pop() # (9)
'Lingnan'
>>> university
['Soochow', 'Nanking', 'Hangchow']
注释(9)中没有向 pop() 提供任何参数,则默认删除列表的最后一个成员,并且以删除了的成员对象作为返回值——提醒读者注意,它有返回值。那么,还是不是对列表原地修改呢?是!——请读者自行验证。不能用是否有返回值来判断是否原地修改,而是要查看其内存地址。
如果参数不为空,可以删除指定索引的成员。
>>> university.pop(1)
'Nanking'
>>> university
['Soochow', 'Hangchow']
对这两个删除方法简要总结如下:
remove(x) 的参数 x 是列表中的成员,不可省略,对列表原地修改,无返回值。pop([index]) 中的 index 是列表中成员的索引值,省略则删除列表最后一个成员,否则删除索引为 index 的成员,且返回删除对象,亦是原地修改。第三个要介绍的是 clear() 方法,它的作用是将列表中成员“清扫干净”,只剩下一个空列表。
>>> lst = [1, 2, 3]
>>> id(lst)
140442363810560
>>> lst.clear()
>>> lst
[]
>>> id(lst)
140442363810560
调用方法很简单,值得注意的是,在执行了列表的 clear() 方法之后,当前列表变成了空列表,并且列表的内存地址没有改变——仍然是原地修改。
如果按照下面的操作,也得到空列表,请读者思考,与上面的操作有何不同?
>>> temp = ['a', 'b']
>>> temp = []
>>> temp
[]
提示,若要解决上述问题,需要结合 Python 中对变量的认识,即第2章2.3节中已经自学过的内容——Python 中变量与对象之间是引用关系。
3. 成员排序
虽然列表的成员已经具备某种顺序——索引,但是,在实际应用中常常要将其按照新的顺序排列。比如由若干个人的姓名组成的列表,通常需要将列表按照人名的某种顺序排列(常见的按照字典中的拼音顺序排列)。又因为列表是可变的,这个特点为实现其排序提供了便利条件。
对列表排序可以通过其方法 sort() 实现:
>>> lst = [6, 1, 5, 3]
>>> lst.sort()
>>> lst
[1, 3, 5, 6]
列表的 sort() 方法让列表中的成员重新排序——原地修改,且没有返回值。在默认情况下,上面的操作实现的是从小到大的排序(称为“升序排列”,反之为“降序排列”)。
如果要降序排列,只需在 sort() 方法中设置 reverse=True 即可:
>>> lst.sort(reverse=True)
>>> lst
[6, 5, 3, 1]
这些都是 sort() 方法的简单应用。排序,还有更复杂的方式。如果读者查看 sort() 的帮助文档,会发现它的完整格式为:
sort(self, /, *, key=None, reverse=False)
这里要重点说明参数 key ,它的默认值是 None ,如果要给它提供有意义的值,是什么效果?
>>> lst = ['python', 'java', 'pascal', 'c', 'basic']
>>> lst.sort(key=len) # (10)
>>> lst
['c', 'java', 'basic', 'python', 'pascal']
注释(10)中的 len 是 Python 内置函数 len() 的函数名称,key = len 的含义为参数 key 引用此函数对象——在 Python 中,名称即代表对象,函数也是对象(参阅第7章7.3.1节)。len() 函数可以得到对象的长度,注释(10)即根据列表中成员的长度进行升序排列。如果不指定排序关键词,是按照字母顺序排序。对比如下结果:
>>> lst = ['python', 'java', 'pascal', 'c', 'basic']
>>> lst.sort()
>>> lst
['basic', 'c', 'java', 'pascal', 'python']
对于排序而言,Python中还提供了一个内置函数 sorted() ——请注意拼写。
>>> lst = ['python', 'java', 'pascal', 'c', 'basic']
>>> sorted(lst)
['basic', 'c', 'java', 'pascal', 'python']
>>> lst
['python', 'java', 'pascal', 'c', 'basic']
是否能看出 sorted() 函数与列表的 sort() 方法之间的差异?用 sorted() 函数对列表排序之后,得到了一个新的列表对象,原列表没有变化。
不仅如此,sorted() 函数的参数可以是任何可迭代对象,比如:
>>> sorted('python')
['h', 'n', 'o', 'p', 't', 'y']
4. 反转
读者在4.3.1节已经熟知了一种实现成员反转的方法:
>>> a = [3, 2, 5, 7]
>>> a[: : -1]
[7, 5, 2, 3]
>>> a
[3, 2, 5, 7]
这并不对原来的列表产生任何影响。除了这种反转方法之外,列表也提供了一种方法 reverse(),其调用方式比较简单。
>>> a.reverse()
>>> a
[7, 5, 2, 3]
注意,依然是原地修改,它没有返回值。
Python 的内置函数 reversed() 其实也能实现同样效果——注意函数名称拼写。
>>> reversed(a)
<list_reverseiterator object at 0x7fbb494642b0>
返回的东西,是此前没有遇到过的,在此后会经常遇到,请读者务必耐心地阅读:list_reverseiterator object ,其意思是说返回了一个迭代器对象(参阅第9章9.6节),且此对象是由对列表反转生成的。
可以用 list() 函数将 reversed(a) 的返回值转换为列表,即可“看到”其内容:
>>> a
[7, 5, 2, 3]
>>> list(reversed(a))
[3, 2, 5, 7]
>>> a
[7, 5, 2, 3]
以上所介绍的有关列表的方法,共同特点是实现列表原地修改——“筐”不变,里面的成员变化:重新排序、增加、删除。此外, index() 和 count() 两个方法,因为与4.2.6节曾经介绍过的字符串的方法雷同,不再单独演示,请读者自行探究。还有一个留待后面介绍的方法 copy() ,它会涉及到一个较为隐蔽的问题,此处暂不泄露天机。
★自学建议 在学校里学习的时候,“做笔记”被公认为是一种好的学习方法,现在自学编程语言亦然。“不动笔墨不读书”同样适用于电子产品泛滥的今天,只不过现在“笔墨”选择更多了,不再局限于纸、笔,可以把学习笔记写在博客、电脑中的文档或者某个笔记应用中。曾经有一名读者,在阅读《跟老齐学Python:轻松入门》那本书时,对每个专题内容都做了非常完整的笔记,并发布到网络上,其中不仅包括从书中所学,还包括查阅其他资料所得。这不仅仅是知识的积累,也是他学习历程的记载。 建议读者边学边做笔记,积累经验,提升能力,增长见识。 ”