一、列表(list)数据类型讲解
1.列表是什么
列表是一种有序且可变的数据类型,它是一种序列类型,也是一种容器类型,所谓容器类型,是指它如容器一样,可以存储int,float,bool,str等等类型的数据(支持存储所有的数据类型,包括列表自身,也包括自定义的),后面要学习的元组、集合、字典等都属于容器类型,他们具有相似的特点。
列表使用中括号[]进行定义,各项元素之间使用逗号分隔。python的列表与其他编程语言中的数组很像,但独特之处在于python并不要求列表中的元素必须都是同一个类型,而是可以存储任意类型的数据。
列表和字符串都是序列,其内元素是按索引顺序进行存储的,同样支持索引方式访问,也支持切片操作,相较字符串的不同之处在于字符串不可修改,是不可变对象,而列表是可变对象,你可以修改列表的内容,这不会让列表有任何元数据的变动。
在定义列表时,不需要指定列表的长度,也不用担心持续向列表中写入新的数据会达到存储容量限制,python列表是动态扩容的。
2.创建列表
#创建python列表有两种方式:
#第一种,使用[] 创建列表
lst1 = [] #这是创建了一个空列表
lst2 = [1, '2', True, [1, 2]] #该列表中有数值、字符串、布尔值、列表
lst3 = [
1,
'2',
3
]
#使用[]中括号构建列表时,列表的元素可以跨行书写,这是python语法中各种括号类型的特性。
#第二种,使用内置函数list()创建列表
lst1 = list("python")
lst2 = list([1, 2, 3])
print(lst1) # ['p', 'y', 't', 'h', 'o', 'n']
print(lst2) # [1, 2, 3]
#能够作为list参数的不只是字符串和列表,任何可迭代对象都可以作为list函数的参数,list会返回一个新的列表
#当list()函数的参数为字符串时,得到的结果中,每个字符作为列表的一个元素,而不是整串字符作为一个元素。可以通过.append()方法实现整个字符串作为单个元素放入列表,如 print([].append(lst1))的结果是["python"]
x="asfdsa","aaa" #这里的变量x赋值为两个字符串,实际上变量x就成了元组类型,print(type(x))的结果是<class 'tuple'>
print(list(x))
['asfdsa', 'aaa']#元组会自动解包
print(list(range(0, 4))) # [0, 1, 2, 3]
#上面range()用于生成一系列数值,就像Linux下的seq命令一样。但是range()不会直接将数据生成出来,它返回的是一个可迭代对象,表示可以一个一个地生成这些数据,所以这里使用list()将range()的数据全部生成出来并形成列表。
#中括号[]方式构造列表有一个很重要的特性:列表解析,也称为"列表推导式"。相比list()是直接将所给定的数据一次性全部构造出来,直接在内存中存放整个列表对象,列表推导方式构造列表比list()要快,且性能差距还挺大的,后面会讲到
3.访问修改列表(索引和切片)
#访问列表里的元素只能使用索引方式,它的一般代码形式如下
list[index]
#在列表对象后面紧跟一对中括号[],中括号里的index是索引值,索引从0开始,正向索引逐个递增,列表的最大索引值是列表的长度减去1。python列表的索引支持反向索引,最后一个元素的索引值是-1,反向递减。
现在有一个列表 L=[0,1,2,3,4,5,6,7,8,9]
列表第一个元素:L[0]或者L[-10]
列表最后一个元素:L[9]或者L[-1]
列表更新元素: 直接list[下标]=值 即可赋值,L[0]=9 #L的新结果是[9,1,2,3,4,5,6,7,8,9]
列表删除元素:del list[下标],必须注意,del和list之间,不要用点或者逗号,直接是空格
#列表嵌套时,支持多级索引访问元素的值
L = [
... [1,2,3,4],
... [11,22,33,44],
... [111,222,333,444]
... ]
print(L[0][2]) # 3
print(L[1][2]) # 33
print(L[2][2]) # 333
列表可以切片: list[start:end:step]
start:起始索引,从0开始,-1表示结束
end:结束索引,-1表示结束
step:步长,end-start,步长为正时,从左向右取值。步长为负时,反向取值
print(['c','d','e','f'][::-1])的结果是['f','e', 'd', 'c']
print(['cdef'][::-1])的结果是['cdef'] #列表中只有一个元素,逆序是把每个元素作为一个整体逆序,不会对元素内部逆序
#切片本身要用中括号[]完成,如果列表不用变量代指的话,那就会有至少两个[]并列,第一个是列表,第二个切片操作
#关于列表切片的其他内容,参考字符串切片即可,异曲同工。
#前面提到,可以按索引的方式给列表元素赋值,从而修改列表。
#通过赋值方式修改列表元素时,不仅可以单元素赋值修改,还可以多元素切片赋值。
#但要注意 ①切片是左闭右开,②实际上是先删除(指定切片范围),再插入(自定义范围),也就是说,切片赋值后,元素数量可能会变。
>>> L = [1,2,3,4,5]
>>> L[0] = 11
>>> L[1:3] = [22,33,44,55]
>>> L
[11, 22, 33, 44, 55, 4, 5]
上面对列表的切片进行赋值时,实际上是先取得这些元素,删除它们,并插入新数据的过程。所以上面是先删除[1:3]的元素,再在这个位置处插入新的列表数据。
所以,如果将某个切片赋值为空列表,则表示直接删除这个元素或这段范围的元素。
>>> L=[11, 22, 33, 44]
>>> L[1:3] = []
>>> L
[11, 44]
但如果是将空列表赋值给单个索引元素,这不是表示删除元素,而是表示将空列表作为元素嵌套在列表中。
>>> L = [1,2,3,4]
>>> L[0] = []
>>> L
[[], 2, 3, 4]
这两种列表赋值的区别,在理解了前文所说的列表结构之后应该不难理顺。
有时在取 list 中元素时可能会遇到以下情形:
a=[]
a[0] 报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
这种情况发生是因为只定义了一个空列表,没有进行任何的赋值,所以列表里什么也没有,而后面的报错也明确表示了索引超出范围,即写上的0其实是第一个元素的位置,而此时是空列表,没有元素,故而报错。
而如果我们使用以下语句则不会报错:
a[0:]#结果是[]
这个不是什么小技巧,这是不一样的语句意义,这句话其实是把这个列表 a 里的所有值都输出,其效果和 a[:]的最终结果是一致的。
3.1 理解切片左闭右开的科学性
不论是字符串的切片操作还是列表的切片操作,即使指定了切片范围的结束索引,切片操作生成的新对象也不会包括结束索引位置的元素,切片的索引范围是左闭右开的,为什么有这样的设定和要求,包含结束索引位置的元素就不行么?
之所以要求切片索引范围做到左闭右开,根本原因在于索引是从0开始的,左闭右开将有如下好处:
- 如果只指定结束索引,那么很容易就看出切片的长度,比如lst[:3],切片后生成的新列表长度就是3
- 如果指定了开始索引和结束索引,那么很容易就能够计算出切片的长度,比如lst[2:7], 切片的长度是7-2 = 5
- 左闭右开很容易通过一个索引将列表切分成两份,比如lst[:5] 和 lst[5:]
3.2 理解嵌套列表
python嵌套列表是一个对初学者稍有困难的知识点,所谓嵌套,是指列表里出现了其他容器类型数据,比如下面的例子
lst = [1, [1, 5], 3, [9, 8, [1, 3]], 5]
print(lst[3][2][0])
print(lst[1:4])
在前面讲列表的创建时,特别强调了,列表用中括号创建,列表里的数据用逗号分隔,从左往右看,第一个数据是1, 这个没有问题,关键是第二个数据,到底是[1, 5], 还是[1 ? 他们都被逗号分隔了,实际上,第二个数据是[1, 5],因为[1, 5]是一个列表,是一个数据,而[1 不是一个数据,我们已经学过的数据类型里没有这种数据类型。
按照上面的思路去思考,列表里的数据如下
索引 | 数据 |
---|---|
0 | 1 |
1 | [1, 5] |
2 | 3 |
3 | [9, 8, [1, 3]] |
4 | 5 |
在此基础上理解lst[3][2][0]
- lst[3] 的值是[9, 8, [1, 3]]
- [9, 8, [1, 3]] 是一个列表,列表里有3个数据,索引为2的数据是[1, 3]
- [1, 3]是一个列表,列表里有两个数据,索引为0的数据是1
- print(lst[3][2][0]) 语句输出的结果是1
现在,请不用代码,自己手写出下面语句的结果
- print(lst[1:4])
- print(lst[3][2:])
- print(lst[-2][1:2])
答案是
[[1, 5], 3, [9, 8, [1, 3]]]
[[1, 3]]
[8]
3.3 理解列表是可变对象
>> L = ["a", "b", "c"]
>>> id(L), id(L[0])
(57028736, 55712192)
>>> L[0] = "aa"
>>> id(L), id(L[0])
(57028736, 56954784)
从id的变动上看,修改列表的第一个元素时,列表本身的id没有改变,变的只是列表第一个元素的id。(id是对象在内存中的地址,id()是返回一个对象的“标识”)
看了下面列表的内存图示就很容易理解了。
上面是L = ["a", "b", "c"]
列表的图示。变量名L存储了列表的内存地址,列表内部包含了类型声明、列表长度等元数据,还保存了属于列表的3个元素的内存地址。需要注意的是,列表元素不是直接存在列表范围内的,而是以地址的形式保存在列表中。
所以,修改列表中的元素时,新建一个元素"aa"(之所以新建,是因为字符串是不可变类型),列表本身并没有改变,只是将列表中指向第一个元素的地址改为新数据"aa"的地址。
因为修改列表数据不会改变列表本身属性,这种行为称为"原处修改"。
所以,列表有几个主要的的特性:
-
列表中可以存放、嵌套任意类型的数据
-
列表中存放的是元素的引用,也就是各元素的地址,因此是列表可变对象
-
列表是可变序列。所以各元素是有位置顺序的,可以通过索引取值,可以通过切片取子列表
4.列表常用运算符
列表支持+ *
符号操作:
>>> L = [1,2,3,4]
>>> L1 = ['a','b','c']
>>> L + L1
[1, 2, 3, 4, 'a', 'b', 'c']
>>> [1,2] + list("34")
[1, 2, '3', '4']
>>> L * 2
[1, 2, 3, 4, 1, 2, 3, 4]
>>> 2 * L
[1, 2, 3, 4, 1, 2, 3, 4]
#对于列表来说,通过*,重复的是列表内的元素,而不是产生嵌套列表,不要认为 [1]*2 的结果是 [[1],[1]]
#对于字符串来说,通过*,"a"*3 得到的是 "aaa",而不是"a""a""a"
可以通过+=
的方式进行二元赋值:
>>> L1 = [1,2,3,4]
>>> L2 = [5,6,7,8]
>>> L1 += L2
>>> L1
[1, 2, 3, 4, 5, 6, 7, 8]
L1 += L2
的赋值方式对于可变序列来说(比如这里的列表),性能要好于L1 = L1 + L2
的方式。前者直接在L1的原始地址内进行修改,后者新创建一个列表对象并拷贝原始L1列表。当然实际上,性能的差距是微乎其微的,前面说过列表中保存的是元素的引用,所以拷贝也仅仅只是拷贝一些引用,而非实际数据对象。
使用成员运算符in判断某个元素是否在列表中
lst1 = [1, 2, 3]
print(2 in lst1) # True
print(3 not in lst1) # False
Python 表达式 | 结果 | 描述 |
---|---|---|
[1, 2, 3] + [4, 5, 6] | [1, 2, 3, 4, 5, 6] | 组合 |
['Hi!'] * 4 | ['Hi!', 'Hi!', 'Hi!', 'Hi!'] | 重复 |
3 in [1, 2, 3] | True | 元素是否存在于列表中 |
5.列表相关的函数和方法
列表是一种序列,所以关于序列的操作,列表都可以用,比如索引、切片、各种序列可用的函数(如append()、extend()、remove()、del、copy()、pop()、reverse()等)。除了这些序列通用操作,列表还有一个专门的列表方法sort需要特别注意,我们知道,python中有个内置函数叫sorted(),支持各种容器类型,这里sort()方法则是列表特有的,虽然它们都可以排序,且用法类似,但sort()是在原地排序的,不会返回排序后的列表,而sorted()是返回新的排序列表,具体详情后面会讲。
序号 | 方法是列表这种数据类型的功能函数,使用形式如: 列表.方法名(参数) |
---|---|
1 | len(list) 列表元素个数,返回值是整数类型,经常通过len求元素数量后,用 for i in range(len(n)) 生成循环。 |
2 | max(list) 返回列表元素最大值 |
3 | min(list) 返回列表元素最小值 |
4 | list(seq) 将元组转换为列表,注意字符串转成列表后,字符会被分割成多个列表元素,而不是只有一个元素。但是如果一个字符串变量指向的是多个字符串,那么转换为列表后,一个完整的字符串变成一个列表元素,而不是细切割。 |
1 | list.append(obj) 在列表末尾添加新的对象,一次只能加一个元素,不能一个append方法里面放多个元素。append是浅拷贝。 |
2 | list.count(obj) 统计某个元素在列表中出现的次数 |
3 | list.extend(seq) 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表),一次只能加一个序列,不能一个extend方法里面放多个序列。append是浅拷贝。 |
4 | list.index(obj) 从列表中找出某个值第一个匹配项的索引位置,如果该元素不存在,则会引发ValueError,可以通过start和end指定搜索的范围,但是返回的索引值,依然是全范围的索引,,比如[1,2,3,4].index(3)的结果为2,[1,2,3,4].index(3,2)的结果依然是2。 |
5 | list.insert(index, obj) 将对象插入列表,如果增加的是列表中的一个元素(子列表),则新增的元素初始只作为原元素的一个镜像,这时候如果修改原元素(子列表)中的一个子元素,则新增元素同样变化,修改新元素中的子元素也是如此。 |
6 | list.pop(index=-1]) 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值,可以通过打印pop()函数返回它删除的值,比如print([1,2,3,4].pop(1))的结果是2,元素是什么类型就返回什么类型,并不是返回一个列表类型。 |
7 | list.remove(obj) 移除列表中某个值的第一个匹配项 |
8 | list.reverse() 对原列表进行反向排序,没有返回值,反向列表中元素,如[1,2,3,4].reverse()的结果是[4,3,2,1] |
9 | list.sort( key=None, reverse=False) 对原列表进行排序,没有返回值,reverse = True 降序, reverse = False 升序(默认)。注意:sort()方法必须单独使用,不能和其他语句混合到一个语句里,否则返回None,比如print([1,2,3,4].sort()),就没有打印出来值,必须先list.sort()一行之后,再进行print(),原因是该方法没有返回值。 |
10 | list.clear() 清空列表,不是删除,清空后变成空列表 |
11 | list.copy() 复制列表,一种浅拷贝 |
5.1 使用len函数获取长度
lst = [1, 2, 3, 2]
print(len(lst)) # 4
知晓列表的长度,就能够计算出列表的索引范围,列表的最小索引是0,最大索引是起长度值减1,这在通过索引方式遍历列表时是非常有用的
lst = [1, 2, 3, 2]
for i in range(len(lst)):
print(lst[i])
这段代码如果现在不能理解,没有关系,等学习到for循环后再回看这段代码就一目了然了。
5.2 增加列表中的元素
向一个列表新增数据,除了用操作符+外,有三个常用的方法,他们有各自的应用场景,这三个方法分别是
- append
- insert
- extend
append方法在列表的末尾新增数据
lst = [1, 2, 3]
lst.append(4)
print(lst) # [1, 2, 3, 4]
新增的数据一定会成为列表的最后一个元素,append是追加的意思,使用该方法时,无需关心列表目前的元素个数,只需明确一点,新的元素一定是在列表末尾增加, append方法没有返回值,但会修改原来的列表。
如果你想在列表中间的某个位置上插入一个新的元素,需要使用insert方法。
insert方法在指定的索引位置的前面向列表中插入一个新的元素,指定的插入索引位置是多少,新元素的索引就是多少。
lst = [1, 2, 3]
lst.insert(0, 4)
print(lst) # [4, 1, 2, 3]
在这个例子中,我使用insert方法在索引0的前面插入新元素4,插入后,4的索引就是0。由于insert只能在指定索引的前面插入,因此你无法使用insert方法在列表的末尾插入新的元素,列表的最大索引是2,如果insert指定插入索引位置为2,那么新插入元素的索引就会是2,而原本在索引2的位置上的元素将被移动到索引3的位置上。
append和inset一次只能新增一个元素,extend方法可以一次性将另一个序列里的元素追加到列表的末尾
lst1 = [1, 2, 3]
lst2 = [4, 5, 6]
lst1.extend(lst2)
print(lst1) # [1, 2, 3, 4, 5, 6]
这里要特别强调一点,extend方法的参数不仅限于列表,还可以是元组,字符串,集合,字典, 等序列
lst = []
lst.extend([1, 2]) # 列表
print(lst)
lst.extend((3, 4)) # 元组
print(lst)
lst.extend('56') # 字符串
print(lst)
lst.extend(set([1, 3, 4])) # 集合
print(lst)
lst.extend({'age': 5}) # 字典
print(lst)
程序输出结果为
[1, 2]
[1, 2, 3, 4]
[1, 2, 3, 4, '5', '6']
[1, 2, 3, 4, '5', '6', 1, 3, 4]
[1, 2, 3, 4, '5', '6', 1, 3, 4, 'age']
当参数是字典时,只会将字典的key增加到列表中, extend方法没有返回值,但会在原列表里添加新的列表内容, 可以扩增原列表的数据
5.3 删除列表中的数据
删除列表中的数据,有4种方法,分别是
- remove
- pop
- del
- clear
列表remove方法
列表的remove方法会将指定元素从列表中删除,如果这个元素在列表中存在多个,则删除索引最小的那一个,也就是列表中第一个与指定元素相同的数据。
lst = [3, 5, 3, 6, 5,"6",6]
lst.remove(6)
print(lst) # [3, 5, 3, 5,"6",6]
lst.remove(5)
print(lst) # [3, 3, 5,"6",6]
lst.remove("6")
print(lst) # [3, 3, 5,6]
如果被删除的元素在列表中不存在,则会引发ValueError异常,因此在使用remove方法之前,务必先使用成员运算符in 或者是其他方法先确定被删除的元素是否真的在列表中。
lst = [3, 5, 3, 6, 5]
lst.remove(7) # 列表中没有7
#返回报错信息
Traceback (most recent call last):
File "C:\Users\ZY922\AppData\Roaming\JetBrains\PyCharm2022.2\scratches\00.py", line 49, in <module>
lst.remove(7) # 列表中没有7
ValueError: list.remove(x): x not in list
列表pop方法
python列表 (list) pop方法删除列表中指定索引的元素并且返回该元素,如果不指定索引位置,则默认删除列表中最后一个元素
lst = [1, 2, 3]
print(lst.pop()) # 默认删除最后一个元素, 3
print(lst.pop(0)) # 删除索引为0的元素, 1
print(lst) # [2]
如果列表是空的,使用pop方法会怎样呢?由于没有元素可以被删除,因此程序会报错
lst = []
lst.pop()
#一个空列表使用pop方法会引发IndexError异常,报错内容为
Traceback (most recent call last):
File "/Users/kwsy/kwsy/coolpython/demo.py", line 2, in <module>
lst.pop()
IndexError: pop from empty list
使用del关键字也可以删除指定索引位置的元素
lst = [1, 2, 3, 2]
del lst[2]
print(lst) # [1, 2, 2]
#如果索引值超出范围,也会报错
clear方法清空列表,删除所有元素
lst = [1, 2, 3, 2]
lst.clear()
print(lst) # []
clear方法清空列表后,lst变为一个空列表,但不会删除掉这个列表。
5.4 关于比较大小
字符串也好,列表也好,都是可迭代对象。比较大小的时候,先比较两个对象的第1个元素(下标或者说索引为0),大小关系即为对象的大小关系,如果相等则继续比较后续元素,一旦不相等,终止比较给出结果。字符串的比较是比较ASCII码值 ,哪个字符的ASCII码值大,哪个字符串就大。另外也可通过内置函数 ord() 获得每个字符的 Unicode 编码进行大小比较。汉字>小写字母>大写字母(小写字母始终比大写字母大32)>数字,有字符比没有字符要大,如‘abc’<’ab’的结果为False。
注意:如果列表/元组里面,各种数据类型混杂,则无法用max或min来比较大小或排序,如 max([1,'2'])会报错 not supported between instances of 'str' and 'int',
如果一定要把字符串和数字混杂的列表进行排序/比大小,可以都转成同样的类型后再比较,或者用 .sort()方法的第一个参数实现:
a = [-1, -3, 1, 0, 3, 'a', 'b', 'c']
a.sort(key = str)
print(a)
#结果是[-1, -3, 0, 1, 3, 'a', 'b', 'c']
5.5 列表排序
列表排序在两种方法,既可以用列表专属的方法sort()和reverse()方法,也可以用Python内置的函数sorted()
列表的reverse方法
列表reverse方法可以将列表里的元素翻转,这种翻转只是将首尾元素对换位置,reverse方法是一种特殊的排序,只能排成倒序
lst = [1, 2, 3]
lst.reverse()
print(lst) # [3, 2, 1]
print([1,0,1,1].reverse()) #结果是[1,1,0,1],不要认为是[0,1,0,0](类似二进制按位取反)
其实,翻转的算法非常容易就能写出来,下面是一个简单的实现
lst = [1, 2, 3]
for i in range(0, len(lst)//2):
lst[i], lst[len(lst)-1-i] = lst[len(lst)-1-i], lst[i]
print(lst)
# 如果是奇数个元素,那最中间的元素不需要处理;虽然range()是左闭右开,但是索引值比长度值要-1,所以len(lst)//2这个索引不用取
# len(lst)-1 是因为索引是从0开始,长度值-1才是索引值;
列表的sort方法
列表的sort方法可以对列表里的元素进行排序, 它有两个重要的参数,一个是key, 一个是reverse, key来设置一个函数用于返回用于比较大小的数值, reverse参数决定排序是从小到大还是从大到小, 下面是一个简单的sort方法使用示例。
lst = [2, 1, 4, 3]
lst.sort()
print(lst) # [1, 2, 3, 4]
sort方法的定义如下
def sort(self, key=None, reverse=False):
pass
参数key指定了排序所用的数值,reverse设置排序的方法,默认为False表示从小到大排序,对上面的列表从大到小排序可以这样写
lst = [2, 1, 4, 3]
lst.sort(reverse=True)
print(lst) # [4, 3, 2, 1]
列表里的元素都是整数,可以直接比较大小,因此用不上key这个参数,如果列表里的元素无法直接进行大小比较,就必须指定参数key,参数key必须是一个函数,这个函数用来把列表中无法进行大小比较的元素转换成可以用来比较大小的元素。
#字符串与整数无法直接比较大小,在比较大小时,应比较他们转成字符串之后的结果,如下例
lst = ['3', 5, '1', 2]
lst.sort(key=lambda x:str(x))
print(lst) # ['1', 2, '3', 5]
#lambda表达式后续会学到,这里只要知道,lambda x:str(x)的作用是把列表中的元素转成字符类型,返回的是一个<function <lambda> at 0x00000256A49D0C10>
除了使用lambda表达式,还可以使用自定义函数
列表里的数据是元组,元组之间无法直接比较大小,因此使用自定义函数compare指定使用元组的第一个元素代表元组进行大小比较来排序
lst = [(1, 2), (3, 2), (2, 4)]
def compare(x):
return x[0]
lst.sort(key=compare)
print(lst) # [(1, 2), (2, 4), (3, 2)]
容器类型通用的sorted()函数排序
列表排序除了用其特有的sort()方法外,都可以用sorted()函数。sorted()是内置函数,支持各种容器类型。它们都可以排序,且用法类似,但是要注意,sort()是对L直接原地排序的,不是通过返回值来体现排序结果的,所以无需赋值给变量,而sorted()则是返回排序后的新结果,需要赋值给变量才能保存排序结果。
>>> L = ['python', 'shell', 'Perl', 'Go', 'PHP']
>>> sorted(L)
['Go', 'PHP', 'Perl', 'python', 'shell']
>>> L
['python', 'shell', 'Perl', 'Go', 'PHP']
>>> L.sort()
>>> L
['Go', 'PHP', 'Perl', 'python', 'shell']
不难发现,sort()和sorted()默认都是升序排序的(A<B<...<Z<a<b<...<z
)。它们都可以指定参数reverse=True
来表示顺序反转,也就是默认得到降序:
>>> L.sort(reverse=True)
>>> L
['shell', 'python', 'Perl', 'PHP', 'Go']
在python 3.x中,sort()和sorted()都不允许对包含不同数据类型的列表进行排序。也就是说,如果列表中既有数值,又有字符串,则排序操作报错。
sort()和sorted()的另一个参数是key
,它默认为key=None
,该参数用来指定自定义的排序函数,从而实现自己需要的排序规则。
例如,上面的列表不再按照默认的字符顺序排序,而是想要按照字符串的长度进行排序。所以,自定义这个排序函数:
>>> def sortByLen(s):
... return len(s)
然后通过指定key = sortByLen
的参数方式调用sort()或sorted(),在此期间还可以指定reverse = True
:
>>> L = ['shell', 'python', 'Perl', 'PHP', 'Go']
>>> sorted(L,key=sortByLen)
['Go', 'PHP', 'Perl', 'shell', 'python']
>>> L.sort(key=sortByLen,reverse=True)
>>> L
['python', 'shell', 'Perl', 'PHP', 'Go']
再例如,按照列表每个元素的第二个字符来排序。
def f(e):
return e[1]
L = ['shell', 'python', 'Perl', 'PHP', 'Go']
sorted(L, key=f)
L.sort(key=f)
更多的排序方式,参见:sorting HOWTO。比如指定两个排序依据,一个按字符串长度升序排,长度相同的按第2个字符降序排。用法其实很简单,不过稍占篇幅,所以本文不解释了。
6.列表推导式/列表解析
列表解析,它指的是对序列中(如这里的列表)的每一项元素应用一个表达式,并将表达式计算后的结果作为新的序列元素(如这里的列表)。通俗一点的解释,以列表序列为例,首先取列表各元素,对每次取的元素都做一番操作,并将操作后得到的结果放进一个新的列表中。因为解析操作是一个元素一个元素追加到新列表中的,所以也称为"列表推导",表示根据元素推导列表。
最简单的,将字符串序列中的各字符取出来放进列表中:
>>> [ i for i in "abcdef" ]
['a', 'b', 'c', 'd', 'e', 'f']
这里是列表解析,因为它外面使用的是中括号[]
,表示将操作后的元素放进新的列表中。可以将中括号替换成大括号,就变成了集合解析,甚至字典解析。但注意,没有直接的元组解析,因为元组的括号是特殊的,它会被认为是表达式的优先级包围括号,而不是元组构造符号。
取出元素对各元素做一番操作:
>>> [ i * 2 for i in "abcdef" ]
['aa', 'bb', 'cc', 'dd', 'ee', 'ff']
>>> L = [1,2,3,4]
>>> [ i * 2 for i in L ]
[2, 4, 6, 8]
>>> [ (i * 2, i * 3) for i in L ]
[(2, 3), (4, 6), (6, 9), (8, 12)]
解析操作和for息息相关,且都能改写成for循环。例如,下面两个语句得到的结果是一致的:
[ i * 2 for i in "abcdef" ]
L = []
for i in "abcdef":
L.append(i * 2)
但是解析操作的性能比for循环要更好,正符合越简单越高效的理念。
学过其他语言的同学,估计已经想到了,解析过程中对各元素的表达式操作类似于回调函数。其实在python中有一个专门的map()函数,它以第一个参数作为回调函数,并返回一个可迭代对象。也就是说,也能达到和解析一样的结果。例如:
>>> def f(x):return x * 2
...
>>> list(map(f,[1,2,3,4]))
[2, 4, 6, 8]
#map()函数后续再详细解释。
列表推导式中,最前面的表达式也可以用if else,形式如 [ AA if XX else BB for i in list]
#以未成活的胡杨树一题为例
n=int(input())#总种植数量
m=int(input())#未成活胡杨数量
nums=list(map(int,input().split()))#未成活胡杨树的编号 相当于索引号+1
#首先生成树苗状态的二位数组
state=[0 if i+1 in nums else 1 for i in range(n)]
列表推导式的常见书写形式:
[表达式 for 变量 in 列表]
或者
[表达式 for 变量 in 列表 if 条件]
list=[1,2,3]
new_list=[i**2 for i in list]
结果是一个列表[1, 4, 9]
new2_list=[[float(i),i**2] for i in list]
结果是一个二维列表[[1.0, 1], [2.0, 4], [3.0, 9]]
new3_list=[i**2 for i in list if i>=2]
结果是[4,9]
list1=[1,2,3];list2=[4,5,6]
newlist=[x*y for x in list1 for y in list2]
结果是[4, 5, 6, 8, 10, 12, 12, 15, 18]
#使用列表推导式创建二维列表
list_2d = [ [i for i in range(5)] for i in range(5)]
# 结果是 [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]
假设A是一个3行4列即3x4的矩阵/二维列表A,可转换成4x3的列表B
matrix=[[1,2,3,4],[5,6,7,8],[9,10,11,12]] #matrix翻译为矩阵
B=[[row(i) for row in matrix] for i in range(4)]
这个例子中的执行顺序应该为:
for i in range(4):
for row in matrix:
row[i]
即将每一个 matrix 中的列表元素的第一个放在一起、第二个放在一起、
第三个放在一起、第四个元素放在一起作为一个新的列表元素
matrix = [ [7, 2, 9, 4], [5, 6, 9, 8], [9, 10, 11, 12],]
relist1 = [row[i] for i in range(4) for row in matrix]
relist2 = [[row[i] for row in matrix] for i in range(4)]
print(relist1)
print(relist2)
输出:
[7, 5, 9, 2, 6, 10, 9, 9, 11, 4, 8, 12]
[[7, 5, 9], [2, 6, 10], [9, 9, 11], [4, 8, 12]]
relist1 返回为一个单层列表。
relist2。内部循环结果先生成一个列表,并以子列表的形式添加到外层列表中。
7.列表的常见遍历
遍历列表的常用方法是使用for循环,它的模式非常固定
lst = [1, 2, 3, 2]
for item in lst:
print(item)
同时遍历两个或更多的序列,可以使用zip()组合:
A=['姓名','年龄','爱好']
Q=['小明','18','上网']
for i,j in zip(A,Q):
print('问题:',i,'+ 答案:',j)
#输出结果:
问题: 姓名 + 答案: 小明
问题: 年龄 + 答案: 18
问题: 爱好 + 答案: 上网
在序列中遍历时(列表、元组或字符串等都是序列),索引位置和对应值可以使用enumerate()函数同时得到:
for i,j in enumerate(['a','b','c']):
print(i,j)
#结果是一个枚举对象
0 a
1 b
2 c
#例题:取 #或$ 字符的下标构成的列表
arr="asda$asd#asd#as"
index_list=[i for i,j in enumerate(arr) if j in '#$']
print(index)
#得到的是 含有#或$字符的元素的下标构成的列表 [4, 8, 12]
可以参考:https://www.cnblogs.com/yifchan/p/python-1-40.html
统计字符出现的个数或列表内出现的元素次数等也可以用 Counter,一个 Counter 是一个 dict 的子类,用于计数可哈希对象。
from collections import Counter
c = Counter('sadasfas')
print(c)
a=['su','bu','sum','bu','sum','bu']
c = Counter(a)
print(c)
c.update('sadasfas') #添加
print(c)
输出结果:
Counter({'s': 3, 'a': 3, 'd': 1, 'f': 1})
Counter({'bu': 3, 'sum': 2, 'su': 1})
Counter({'bu': 3, 's': 3, 'a': 3, 'sum': 2, 'su': 1, 'd': 1, 'f': 1})
二、练习题
题虽然多,但还是要做。编程,既是一门知识,也是一项技能,仅从学习知识的角度看,许多知识都是一看就懂的,但作为一项技能,它需要你反复练习以达到熟练的程度。就好比骑自行车,坐上去,两脚蹬脚踏板,自行车就可以移动了,这是知识,别人一说你就懂。但是,骑上去就发现,你无法掌握平衡,只有多加练习,才能真正的掌握骑自行车的技术。
1. 列表基础考察
已知一个列表 lst = [1,2,3,4,5]
- 求列表的长度
- 判断6 是否在列表中
- lst + [6, 7, 8] 的结果是什么?
- lst*2 的结果是什么
- 列表里元素的最大值是多少
- 列表里元素的最小值是多少
- 列表里所有元素的和是多少
- 在索引1的位置新增一个的元素10
- 在列表的末尾新增一个元素20
答案如下
1. len(lst)
2. 6 in lst
3. [1,2,3,4,5,6,7,8]
4. [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
5. max(lst)
6. min(lst)
7. sum(lst)
8. lst.insert(1, 10)
9. lst.append(20)
以上都是对列表基础操作,所用到的每一个函数,列表的每一个方法,都是需要你熟记于心的
2. 修改列表
lst = [1, [4, 6], True] 请将列表里所有数字修改成原来的两倍
答案如下
lst[0] = 2
lst[1][0] = 8
lst[1][1] = 12
你以为存在一个函数,其功能便是将列表里所有的数据都变成原来的两倍,这样才显得变成语言是一个非常神奇的东西,但是很遗憾的告诉你,那些神奇的东西都是程序员自己实现的。
想要修改列表里的数据,必须通过索引对其重新赋值,上面的方法很low,你也可以写一个函数来实现这个功能,我们假设要处理的列表里只int,float,bool,和list数据,不管嵌套几层list,这个函数都应该能正确处理,下面是一段示例代码
def double_list(lst):
for index, item in enumerate(lst):
if isinstance(item, bool):
continue
if isinstance(item, (int, float)):
lst[index] *= 2
if isinstance(item, list):
double_list(item)
if __name__ == '__main__':
lst = [1, [4, 6], True]
double_list(lst)
print(lst)
现在,我们还没有学习到函数,更没有学习到递归函数,这个练习题,你只掌握直接通过索引修改列表即可,等到学习函数后,可以再回到这里做这个练习题。
3. 合并列表
lst = [1,2,3] lst2 = [4,5,6] 不使用 + 号运算符,将lst2合并到lst的末尾,并思考,这个过程中,是否产生了新的列表
答案
lst.extend(lst2)
这个过程中不会产生新的列表,最直观的检验方式就是print(id(lst)),合并前后,lst的内存地址都没有发生变化,只是列表里的内容发生了变化
4. 统计练习
列表lst 内容如下
lst = [2, 5, 6, 7, 8, 9, 2, 9, 9]
请写程序完成下列题目
- 找出列表里的最大值
- 找出列表里的最小值
- 找出列表里最大值的个数
- 计算列表里所有元素的和
- 计算列表里元素的平均值
- 计算列表的长度
- 找出元素6在列表中的索引
答案
1. max(lst)
2. min(lst)
3. lst.count(max(lst))
4. sum(lst)
5. sum(lst)/float(len(lst))
6. len(lst)
7. lst.index(6)
这道题考察的是你对内置函数的理解和运用
下面的题目不允许写代码,仅凭思考来回答
- lst[2:4] 的值是什么
- lst[1: -3]的值是什么
- lst[-5]的值是什么
- lst[:-4] 的值是什么
- lst[-4:] 的值是什么
这个题目主要考察你对列表切片操作的理解
1. [6, 7]
2. [5, 6, 7, 8, 9]
3. 8
4. [2, 5, 6, 7, 8]
5. [9, 2, 9, 9]
列表的切片操作,最关键的一点在于左闭右开,结束位置的数据不会列入结果中
5. 列表操作练习
列表lst 内容如下
lst = [2, 5, 6, 7, 8, 9, 2, 9, 9]
请写程序完成下列操作
- 在列表的末尾增加元素15
- 在列表的中间位置插入元素20
- 将列表[2, 5, 6]合并到lst中
- 移除列表中索引为3的元素
- 翻转列表里的所有元素
- 对列表里的元素进行排序,从小到大一次,从大到小一次
答案
1. lst.append(15)
2. lst.insert(len(lst)//2, 20)
3. lst.extend([2, 5, 6])
4. lst.remove(lst[3])
5. lst = lst[::-1]
6. lst.sort() lst.sort(reverse=True)
6. 复杂列表练习
列表lst 内容如下
lst = [1, 4, 5, [1, 3, 5, 6, [8, 9, 10, 12]]]
不写任何代码,仅凭思考推理来回答下列问题
- 列表lst的长度是多少
- 列表lst中有几个元素
- lst[1] 的数据类型是什么
- lst[3]的数据类型是什么
- lst[3][4] 的值是什么
- 如果才能访问到 9 这个值
- 执行lst[3][4].append([5, 6])后,列表lst的内容是什么,手写出来
- lst[-1][-1][-2]的值是什么
- lst[-2]的值是什么
- len(lst[-1]) 的值是什么
- len(lst[-1][-1])的值是什么
- lst[-1][1:3] 的值是什么
- lst[-1][-1][1:-2]的值是什么
第1题和第2题其实是一个意思,原本统计列表里数据个数不是什么难事,可一旦出现了嵌套列表的情况,有人就分不清了,列表里的数据是以逗号分隔的,lst[3] 是一个列表,其余都是int类型数据,因此lst的长度是4
第3题,lst[1] = 4,是int类型数据 第4题,lst[3] 的数据类型是列表 第5题,lst[3]的值是[1, 3, 5, 6, [8, 9, 10, 12]],仍然是一个列表,其索引为4的数据是[8, 9, 10, 12],是列表 第6题,lst[3][4][1] 第7题,[1, 4, 5, [1, 3, 5, 6, [8, 9, 10, 12, [5, 6]]]],参考5,6两个题目的解答 第8题,lst[-1]的值是[1, 3, 5, 6, [8, 9, 10, 12]], 再次取索引为-1的数据为[8, 9, 10, 12],取索引为-2的数据为10 第9题,5 第10题,5 第11题,4 第12题, [3, 5], lst[-1]的值是[1, 3, 5, 6, [8, 9, 10, 12]] 第13题,[9], lst[-1][-1]的值是[8, 9, 10, 12],切片起始位置索引是1,值为9,结束位置是-2,值为10,由于左闭右开,最终结果是[9]