Python 列表
上一节认识了四种常用数据容器。现在有一个具体需求:课程名单会不断增加、调整或删除,怎样保存这组有顺序的数据,才能方便后续维护?
列表适合解决这个问题。它把多个元素集中到一个变量中,并保留元素顺序。列表有两个重要特点:
- 有顺序
- 可修改
和字符串不同,列表中的元素可以直接增加、删除和修改。
案例:维护课程名单
需求:先保存一组课程名称,再支持按位置读取、增加课程、删除课程和调整顺序。接下来从最小可运行的列表开始。
列表使用 [] 创建,元素之间用逗号分隔。先把课程名称集中保存到一个变量中:
course_list = ["Python", "MySQL", "Linux"]
print(course_list)列表可以保存任意数量的元素,也可以创建空列表。名单暂时没有内容时,就从空列表开始:
提示
列表索引从 0 开始。第一个元素的索引是 0,不是 1。
course_list = []
print(course_list)运行结果:
[]列表可以保存不同类型的数据,也可以继续保存其他列表:
number_list = [1, 2, 3, 4, 5]
data_list = ["Python", 95.5, True, number_list]
print(data_list)类型转换
使用 list() 可以把字符串等可遍历内容转换成列表:
text = "Python"
char_list = list(text)
print(char_list)运行结果:
['P', 'y', 't', 'h', 'o', 'n']索引与切片
如果需要读取名单中的某个人,或者只取出其中一段数据,就要按位置访问。列表属于序列类型,支持索引取值和切片。
索引访问
列表中的每个元素都有一个位置编号,这个编号叫作 索引。索引从 0 开始,负数索引从末尾开始。
course_list = ["Python", "MySQL", "Linux"]
print(course_list[0])
print(course_list[-1])运行结果:
Python
Linuxcourse_list[0] 取得第一个元素,course_list[-1] 取得最后一个元素。
提示
按索引取值时,如果索引超出范围,程序会抛出 IndexError。
切片访问
切片就是按范围取出一部分数据。字符串、列表、元组这些序列都支持切片。
格式是 列表[start:stop:step],也就是 列表[起始位置:结束位置:步长]。
start:起始索引stop:结束索引step:步长,默认是1
number_list = list(range(10))
print(number_list[2:5]) # 索引 2 到索引 5,不包含索引 5
print(number_list[2:-2]) # 结束位置也可以使用负数索引
print(number_list[:5]) # 省略起始位置,默认从头开始
print(number_list[5:]) # 省略结束位置,默认取到末尾
print(number_list[::2]) # 每隔一个元素取一次
print(number_list[::-1]) # 步长为 -1,得到倒序结果运行结果:
[2, 3, 4]
[2, 3, 4, 5, 6, 7]
[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[0, 2, 4, 6, 8]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]切片范围与越界
如果要核对切片到底取到了哪些元素,可以结合下面的图和代码逐项观察。规则只有一句:切片包含起始位置,不包含结束位置。
letter_list = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
# 逐个取出索引 1、2、3,与切片 [1:4] 的结果相同
print([letter_list[1], letter_list[2], letter_list[3]])
print(letter_list[1:4])
# 正数索引和对应的负数索引,可以得到相同范围
print(letter_list[1:4])
print(letter_list[-8:-5])
# 省略起始位置或结束位置
print(letter_list[:5])
print(letter_list[4:])
print(letter_list[:])
# 切片超出列表范围不会报错,只会取到实际存在的位置
print(letter_list[4:10000])
# 使用步长控制取值方向和间隔
print(letter_list[0:-1:1])
print(letter_list[0:-1:2])
print(letter_list[7:0:-1])
print(letter_list[::-1])这里要区分两种越界情况:
- 单个索引越界会抛出
IndexError - 切片范围越界不会报错,只返回实际能取到的元素
列表增删改查
课程名单会发生哪些变化?可能新增课程、移除课程、修改课程名称,也可能需要查找某个课程的位置。列表方法正是用来完成这些操作的。
方法速查
在 ipython 里定义一个列表,比如 arr = list(),然后输入 arr. 再按 TAB,就能看到列表常用方法。
In [1]: arr = list()
In [2]: arr.
arr.append() arr.count() arr.insert() arr.reverse()
arr.clear() arr.extend() arr.pop() arr.sort()
arr.copy() arr.index() arr.remove()先按“增加、删除、查询、排序、复制”整理最常用的方法:
| 分类 | 关键字 / 函数 / 方法 | 作用 |
|---|---|---|
| 增加 | append(data) | 在末尾追加一个元素 |
insert(index, data) | 在指定索引位置插入元素 | |
extend(other_list) | 把另一个列表的元素逐个追加进来 | |
| 删除 | pop() | 删除并返回最后一个元素 |
pop(index) | 删除并返回指定索引的元素 | |
remove(data) | 删除第一个匹配的元素 | |
clear() | 清空列表 | |
del 列表[index] | 删除指定索引的元素 | |
| 查询 | index(data) | 返回元素第一次出现的索引 |
count(data) | 统计元素出现的次数 | |
len(列表) | 获取列表长度 | |
| 排序 | sort() | 对原列表排序 |
reverse() | 反转原列表 | |
| 复制 | copy() | 复制列表 |
append()、insert()、pop() 和 remove() 最常用,建议先写熟这一组。
注意:列表的大部分方法都是就地修改,也就是直接改原列表。
下面的连续示例展示同一个列表如何逐步发生变化,运行每一步时都要观察原列表的内容:
>>> arr = ['a', 'b', 'c', 'd', 'e']
>>> arr
['a', 'b', 'c', 'd', 'e']
>>> arr.append('f') # 添加 'f' 元素到末尾
>>> arr
['a', 'b', 'c', 'd', 'e', 'f']
>>> arr.insert(0, 'A') # 在索引 0 的位置插入 'A'
>>> arr
['A', 'a', 'b', 'c', 'd', 'e', 'f']
>>> arr.pop() # 默认弹出最后一个元素
'f'
>>> arr
['A', 'a', 'b', 'c', 'd', 'e']
>>> arr.pop(0) # 指定弹出第 0 个元素
'A'
>>> arr
['a', 'b', 'c', 'd', 'e']
>>> arr.remove('d') # 指定删除内容为 'd' 的元素
>>> arr
['a', 'b', 'c', 'e']
>>> arr.index('c') # 查询内容为 'c' 的元素在第几个位置
2
>>> arr.sort(reverse=True) # reverse=True 表示降序排列
>>> arr
['e', 'c', 'b', 'a']元素新增
名单新增课程时,append() 可以在末尾追加,insert() 可以在指定位置插入:
name_list = ["正心", "全栈", "编程"]
name_list.append("Python")
name_list.insert(0, "零基础")
print(name_list)如果想把一个列表的内容加到另一个列表,要分清 append() 和 extend()。
teacher_list = ["老师 1", "老师 2"]
new_teachers = ["老师 3", "老师 4"]
teacher_list.append(new_teachers)
print(teacher_list)teacher_list = ["老师 1", "老师 2"]
new_teachers = ["老师 3", "老师 4"]
for teacher in new_teachers:
teacher_list.append(teacher)
print(teacher_list)teacher_list = ["老师 1", "老师 2"]
new_teachers = ["老师 3", "老师 4"]
teacher_list.extend(new_teachers)
print(teacher_list)append(new_teachers) 会把整个列表当成一个元素;extend(new_teachers) 会把其中的元素逐个追加到原列表。
元素删除
需要移除课程时,先区分“按位置删除”和“按内容删除”。最常见的方法是 pop()、remove() 和 clear()。
>>> name_list = ['正心', '全栈', '编程', '老师 1', '老师 2']
>>> item1 = name_list.pop()
>>> item2 = name_list.pop()
>>> item1, item2
('老师 2', '老师 1')
# pop(index) 可以指定索引进行弹出
>>> name_list
['正心', '全栈', '编程']
>>> item0 = name_list.pop(0)
>>> name_list
['全栈', '编程']
>>> item0
'正心'
# remove 指定内容进行删除
>>> item = name_list.remove('编程') # remove 删除内容是没有返回值的
>>> item
>>> name_list
['全栈']
>>> name_list.remove('编程') # 没有的内容再次删除会报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
# 清空列表
>>> name_list.clear()
>>> name_list
[]pop() 会返回被删除的元素,remove() 只修改原列表,返回值是 None。如果 remove() 找不到指定元素,会抛出 ValueError。
还可以使用 del 按索引删除:
name_list = ["正心", "全栈", "编程"]
del name_list[1]
print(name_list)元素修改与排序
列表是可变类型,所以可以直接按位置改值,也可以整体逆序或排序。下面同时观察这三类操作对原列表的影响。
name_list = ['正心', '老师 2', '老师 3']
# 修改列表元素
name_list[0] = '正心全栈编程'
print(name_list)
name_list.reverse() # 列表逆序输出
print(name_list)
arr = [1, 5, 2, 3, 6, 8]
arr.sort(reverse=True) # 列表排序
print(arr)元素查询与长度
当需求变成“课程是否存在、出现了几次、列表有多长”时,分别使用 index()、count() 和 len()。
>>> arr = ['正心', '老师 2', '老师 3']
# index 获取元素在列表当中的位置
>>> arr.index('老师 2')
1
>>> arr[arr.index('老师 2')]
'老师 2'
# 如果元素不存在就会报错
>>> arr.index('老师 4')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: '老师 4' is not in list
# 统计元素在列表当中出现的次数
>>> arr.count('正心 1')
0
# 获取列表长度
>>> len(arr)
3案例:办公室座位安排
案例:员工与位置匹配
需求:把员工和办公室位置一一对应起来。已知两个等长列表,需要使用相同索引取得对应数据。
name_list = ["邓永明", "廖德超", "张勇", "杨久林", "戴贵富", "秦代坤", "李元东", "田显余"]
site_list = [
"1 号办公室 1 位置", "1 号办公室 2 位置", "1 号办公室 3 位置",
"2 号办公室 1 位置", "2 号办公室 2 位置", "2 号办公室 3 位置",
"3 号办公室 1 位置", "3 号办公室 2 位置",
]
# 把分配结果保存到这个列表
content = []功能分析:
- 使用索引依次取得员工
- 使用相同索引取得对应的位置
- 把“位置 + 员工”组成一个新列表
- 将分配结果追加到
content
参考代码
name_list = ["邓永明", "廖德超", "张勇", "杨久林", "戴贵富", "秦代坤", "李元东", "田显余"]
site_list = [
"1 号办公室 1 位置", "1 号办公室 2 位置", "1 号办公室 3 位置",
"2 号办公室 1 位置", "2 号办公室 2 位置", "2 号办公室 3 位置",
"3 号办公室 1 位置", "3 号办公室 2 位置",
]
content = []
for index in range(len(name_list)):
content.append([site_list[index], name_list[index]])
print(content)案例:员工位置调整
问题:现有办公室座位表 seating,如何从 1 号、2 号办公室各随机抽取一名员工,并重新安排到 4 号办公室?
- 分别从 1 号、2 号办公室随机选取一名员工
- 将两名员工安排到 4 号办公室
- 最后打印调整后的座位表
import random
seating = [
["1 号办公室 1 位置", "戴贵富"],
["1 号办公室 2 位置", "田显余"],
["1 号办公室 3 位置", "李元东"],
["2 号办公室 1 位置", "廖德超"],
["2 号办公室 2 位置", "秦代坤"],
["2 号办公室 3 位置", "杨久林"],
["3 号办公室 1 位置", "邓永明"],
["3 号办公室 2 位置", "张勇"],
]提示
可以先用 random 模块生成随机数:
# 导入标准库中的 random 模块
import random
random.randint(1, 10)random.randint(a, b) 会返回 a 到 b 之间的随机整数,包含两端。
参考代码
import random
# 1. 分别从 1 号、2 号办公室中选取一名员工
person2 = seating.pop(random.randint(3, 5))
person1 = seating.pop(random.randint(0, 2))
print(person1, person2)
# 2. 修改两名员工的位置
person2[0] = "4 号办公室 1 位置"
person1[0] = "4 号办公室 2 位置"
# 3. 把调整后的数据放回座位表
seating.append(person2)
seating.append(person1)
for site in seating:
print(site)案例:随机安排座位
问题:有 N 个人要参加会议,如何随机安排座位,并确保一个位置只分配给一个人?
import random
# 有多少个人
name_list = [
'邓永明', '廖德超', '张勇', '杨久林',
'戴贵富', '秦代坤', '李元东', '田显余'
]
site_list = [
'1 号办公室 1 位置', '1 号办公室 2 位置', '1 号办公室 3 位置',
'2 号办公室 1 位置', '2 号办公室 2 位置', '2 号办公室 3 位置',
'3 号办公室 1 位置', '3 号办公室 2 位置'
]
# 答案放这里
seating = []参考代码
import random
for name in name_list:
index = random.randint(0, len(site_list) - 1)
site = site_list.pop(index)
seating.append([site, name])
print(seating)二维列表
如果座位表、成绩表或文字排版需要同时表示行和列,一个列表就不够了。列表里还可以继续放列表:外层列表表示多行,内层列表表示每一行,这种结构通常叫作二维列表。
classroom = [
["小明", "小红", "小刚"],
["小李", "小王", "小周"],
]
# 先取得第 1 行,再取得第 1 行中的第 2 个元素
print(classroom[0])
print(classroom[0][1])运行结果:
['小明', '小红', '小刚']
小红二维列表遍历
读取二维列表时,要先确定需求:只关心每个元素,还是还需要知道行号和列号。对应有两种常见遍历方式:
classroom = [
["小明", "小红", "小刚"],
["小李", "小王", "小周"],
]
for row in classroom:
for name in row:
print(name, end=" ")
print()classroom = [
["小明", "小红", "小刚"],
["小李", "小王", "小周"],
]
for row_index in range(len(classroom)):
for col_index in range(len(classroom[row_index])):
print(classroom[row_index][col_index], end=" ")
print()只需要读取所有元素时,直接遍历更简单。需要知道行号、列号,或者要修改指定位置时,可以根据索引遍历。
案例:古诗词转置
这个案例的重点是体会“行”和“列”的区别。
print("""横向打印效果:
千山鸟飞绝
万径人踪灭
孤舟蓑笠翁
独钓寒江雪
纵向打印效果:
千万孤独
山径舟钓
鸟人蓑寒
飞踪笠江
绝灭翁雪""")poetry = [
["千", "山", "鸟", "飞", "绝"],
["万", "径", "人", "踪", "灭"],
["孤", "舟", "蓑", "笠", "翁"],
["独", "钓", "寒", "江", "雪"],
]
# 横向打印:先遍历行,再遍历当前行的每一列
for row_index in range(len(poetry)):
for col_index in range(len(poetry[row_index])):
print(poetry[row_index][col_index], end="")
print()
# 纵向打印:先固定列,再依次取得每一行的同一列
for col_index in range(len(poetry[0])):
for row_index in range(len(poetry)):
print(poetry[row_index][col_index], end="")
print()小结
本节先回答五个问题:
- 为什么使用列表?因为它能按顺序保存一组可修改的数据
- 如何取得数据?索引取得单个元素,切片取得一段元素
- 如何批量增加数据?
append()追加一个元素,extend()逐个追加另一个列表的元素 - 如何删除和查询数据?
pop()按位置删除,remove()按内容删除,再配合index()、count()和len()查询 - 如何处理行列数据?使用二维列表,并分清行索引和列索引
练习
- 自己写一个课程名称列表,分别练习
append()、insert()、pop()。 - 写一段代码,取出列表的前 3 个元素、最后 2 个元素、倒序结果。
- 把一个二维列表按行打印一次,再按列打印一次。