在开始本节课的内容之前,我们先给⼤家⼀个编程任务,将⼀颗⾊⼦掷
6000
次,统计每个点数出现的次数。这个任务对⼤家来说应该是⾮常简单的,我们可以⽤1
到
6
均匀分布的随机数来模拟掷⾊⼦,然后⽤6个变量分别记录每个点数出现的次数,相信⼤家都能写出下⾯的代码。
import random
f1 = 0
f2 = 0
f3 = 0
f4 = 0
f5 = 0
f6 = 0
for _ in range(6000):
face = random.randint(1, 6)
if face == 1:
f1 += 1
elif face == 2:
f2 += 1
elif face == 3:
f3 += 1
elif face == 4:
f4 += 1
elif face == 5:
f5 += 1
else:
f6 += 1
print(f'1点出现了{f1}次')
print(f'2点出现了{f2}次')
print(f'3点出现了{f3}次')
print(f'4点出现了{f4}次')
print(f'5点出现了{f5}次')
print(f'6点出现了{f6}次')
看看上⾯的代码,相信⼤家⼀定觉得它⾮常的
“
笨重
”
和
“
丑陋
”
,更可怕的是,如果要统计掷
2
颗或者更多的⾊⼦统计每个点数出现的次数,那就需要定义更多的变量,写更多的分⽀结构。讲到这⾥,相信⼤家⼀定想问:有没有办法⽤⼀个变量来保存多个数据,有没有办法⽤统⼀的代码对多个数据进⾏操作?答案是肯定的,在Python
中我们可以通过容器类型的变量来保存和操作多个数据,我们⾸先为⼤家介绍列表(list
)这种新的数据类型。
定义和使⽤列表
在
Python
中,
列表是由⼀系元素按特定顺序构成的数据序列
,这样就意味着定义⼀个列表类型的变量,
可以保存多个数据
,⽽且
允许有重复的数据
。跟上⼀课我们讲到的字符串类型⼀样,列表也是⼀种结构化的、⾮标量类型,操作⼀个列表类型的变量,除了可以使⽤运算符还可以使⽤它的⽅法。在Python中,可以使⽤
[]
字⾯量语法来定义列表,列表中的多个元素⽤逗号进⾏分隔,代码如下所示。
items1 = [35, 12, 99, 68, 55, 87]
items2 = ['Python', 'Java', 'Go', 'Kotlin']
除此以外,还可以通过
Python
内置的
list
函数将其他序列变成列表。准确的说,
list
并不是⼀个函
数,⽽是创建列表对象的构造器(后⾯会讲到对象和构造器这两个概念)。
items1 = list(range(1, 10))
print(items1) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
items2 = list('hello')
print(items2) # ['h', 'e', 'l', 'l', 'o']
需要说明的是,列表是⼀种可变数据类型,也就是说列表可以添加元素、删除元素、更新元素,这⼀点跟我们上⼀课讲到的字符串有着鲜明的差别。字符串是⼀种不可变数据类型,也就是说对字符串做拼接、重复、转换⼤⼩写、修剪空格等操作的时候会产⽣新的字符串,原来的字符串并没有发⽣任何改变。
列表的运算符
和字符串类型⼀样,列表也⽀持拼接、重复、成员运算、索引和切⽚以及⽐较运算,对此我们不再进⾏赘述,请⼤家参考下⾯的代码。
items1 = [35, 12, 99, 68, 55, 87]
items2 = [45, 8, 29]
# 列表的拼接
items3 = items1 + items2
print(items3) # [35, 12, 99, 68, 55, 87, 45, 8, 29]
# 列表的重复
items4 = ['hello'] * 3
print(items4) # ['hello', 'hello', 'hello']
# 列表的成员运算
print(100 in items3) # False
print('hello' in items4) # True
# 获取列表的⻓度(元素个数)
size = len(items3)
print(size) # 9
# 列表的索引
print(items3[0], items3[-size]) # 35 35
items3[-1] = 100
print(items3[size - 1], items3[-1]) # 100 100
# 列表的切⽚
print(items3[:5]) # [35, 12, 99, 68, 55]
print(items3[4:]) # [55, 87, 45, 8, 100]
print(items3[-5:-7:-1]) # [55, 68]
print(items3[::-2]) # [100, 45, 55, 99, 35]
# 列表的⽐较运算
items5 = [1, 2, 3, 4]
items6 = list(range(1, 5))
# 两个列表⽐较相等性⽐的是对应索引位置上的元素是否相等
print(items5 == items6) # True
items7 = [3, 2, 1]
# 两个列表⽐较⼤⼩⽐的是对应索引位置上的元素的⼤⼩
print(items5 <= items7) # True
值得⼀提的是,由于列表是可变类型,所以通过索引操作既可以获取列表中的元素,也可以更新列表中的元素。对列表做索引操作⼀样要注意索引越界的问题,对于有 N
个元素的列表,正向索引的范围是
0到 N-1
,负向索引的范围是
-1
到
-N
,如果超出这个范围,将引发
IndexError
异常,错误信息为: list index out of range
。
列表元素的遍历
如果想逐个取出列表中的元素,可以使⽤
for
循环的,有以下两种做法。
⽅法⼀:
items = ['Python', 'Java', 'Go', 'Kotlin']
for index in range(len(items)):
print(items[index])
⽅法⼆:
items = ['Python', 'Java', 'Go', 'Kotlin']
for item in items:
print(item)
讲到这⾥,我们可以⽤列表的知识来重构上⾯
“
掷⾊⼦统计每个点数出现次数
”
的代码。
import random
counters = [0] * 6
for _ in range(6000):
face = random.randint(1, 6)
counters[face - 1] += 1
for face in range(1, 7):
print(f'{face}点出现了{counters[face - 1]}次')
上⾯的代码中,我们⽤
counters
列表中的六个元素分别表示
1
到
6
的点数出现的次数,最开始的时候六个元素的值都是 0
。接下来⽤随机数模拟掷⾊⼦,如果摇出
1
点
counters[0]
的值加
1
,如果摇出
2
点counters[1] 的值加
1
,以此类推。⼤家感受⼀下,这段代码是不是⽐之前的代码要简单优雅很多。
列表的⽅法
和字符串⼀样,列表类型的⽅法也很多,下⾯为⼤家讲解⽐较重要的⽅法。
添加和删除元素
items = ['Python', 'Java', 'Go', 'Kotlin']
# 使⽤append⽅法在列表尾部添加元素
items.append('Swift')
print(items) # ['Python', 'Java', 'Go', 'Kotlin', 'Swift']
# 使⽤insert⽅法在列表指定索引位置插⼊元素
items.insert(2, 'SQL')
print(items) # ['Python', 'Java', 'SQL', 'Go', 'Kotlin', 'Swift']
# 删除指定的元素
items.remove('Java')
print(items) # ['Python', 'SQL', 'Go', 'Kotlin', 'Swift']
# 删除指定索引位置的元素
items.pop(0)
items.pop(len(items) - 1)
print(items) # ['SQL', 'Go', 'Kotlin']
# 清空列表中的元素
items.clear()
print(items) # []
需要提醒⼤家,在使⽤
remove
⽅法删除元素时,如果要删除的元素并不在列表中,会引发
ValueError异常,错误消息是: list.remove(x): x not in list
。在使⽤
pop
⽅法删除元素时,如果索引的值超出了范围,会引发 IndexError
异常,错误消息是:
pop index out of range
。从列表中删除元素其实还有⼀种⽅式,就是使⽤Python
中的
del
关键字后⾯跟要删除的元素,这种做法跟使⽤ pop
⽅法指定索引删除元素没有实质性的区别,但后者会返回删除的元素,前者在性能上略优( del
对应字节码指令是
DELETE_SUBSCR
,⽽
pop
对应的字节码指令是
CALL_METHOD 和
POP_TOP
)。
items = ['Python', 'Java', 'Go', 'Kotlin']
del items[1]
print(items) # ['Python', 'Go', 'Kotlin']
元素位置和次数
列表类型的
index
⽅法可以查找某个元素在列表中的索引位置;因为列表中允许有重复的元素,所以列表类型提供了 count
⽅法来统计⼀个元素在列表中出现的次数。请看下⾯的代码
items = ['Python', 'Java', 'Java', 'Go', 'Kotlin', 'Python']
# 查找元素的索引位置
print(items.index('Python')) # 0
print(items.index('Python', 2)) # 5
# 注意:虽然列表中有'Java',但是从索引为3这个位置开始后⾯是没有'Java'的
print(items.index('Java', 3)) # ValueError: 'Java' is not in list
再来看看下⾯这段代码。
items = ['Python', 'Java', 'Java', 'Go', 'Kotlin', 'Python']
# 查找元素出现的次数
print(items.count('Python')) # 2
print(items.count('Go')) # 1
print(items.count('Swfit')) # 0
元素排序和反转
列表的
sort
操作可以实现列表元素的排序,⽽
reverse
操作可以实现元素的反转,代码如下所示。
items = ['Python', 'Java', 'Go', 'Kotlin', 'Python']
# 排序
items.sort()
print(items) # ['Go', 'Java', 'Kotlin', 'Python', 'Python']
# 反转
items.reverse()
print(items) # ['Python', 'Python', 'Kotlin', 'Java', 'Go']
列表的⽣成式
在
Python
中,列表还可以通过⼀种特殊的字⾯量语法来创建,这种语法叫做⽣成式。我们给出两段代码,⼤家可以做⼀个对⽐,看看哪⼀种⽅式更加简单优雅。
通过
for
循环为空列表添加元素。
# 创建⼀个由1到9的数字构成的列表
items1 = []
for x in range(1, 10):
items1.append(x)
print(items1)
# 创建⼀个由'hello world'中除空格和元⾳字⺟外的字符构成的列表
items2 = []
for x in 'hello world':
if x not in ' aeiou':
items2.append(x)
print(items2)
# 创建⼀个由个两个字符串中字符的笛卡尔积构成的列表
items3 = []
for x in 'ABC':
for y in '12':
items3.append(x + y)
print(items3)
通过⽣成式创建列表。
# 创建⼀个由1到9的数字构成的列表
items1 = [x for x in range(1, 10)]
print(items1) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 创建⼀个由'hello world'中除空格和元⾳字⺟外的字符构成的列表
items2 = [x for x in 'hello world' if x not in ' aeiou']
print(items2) # ['h', 'l', 'l', 'w', 'r', 'l', 'd']
# 创建⼀个由个两个字符串中字符的笛卡尔积构成的列表
items3 = [x + y for x in 'ABC' for y in '12']
print(items3) # ['A1', 'A2', 'B1', 'B2', 'C1', 'C2']
下⾯这种⽅式不仅代码简单优雅,⽽且性能也优于上⾯使⽤
for
循环和
append
⽅法向空列表中追加元素的⽅式。可以简单跟⼤家交待下为什么⽣成式拥有更好的性能,那是因为Python
解释器的字节码指令中有专⻔针对⽣成式的指令( LIST_APPEND
指令);⽽
for
循环是通过⽅法调⽤((
LOAD_METHOD
和CALL_METHOD 指令)的⽅式为列表添加元素,⽅法调⽤本身就是⼀个相对耗时的操作。对这⼀点不理解也没有关系,记住“
强烈建议⽤⽣成式语法来创建列表
”
这个结论就可以了。