Skip to content

Python 列表 ​

上一节认识了四种常用数据容器。现在有一个具体需求:课程名单会不断增加、调整或删除,怎样保存这组有顺序的数据,才能方便后续维护?

列表适合解决这个问题。它把多个元素集中到一个变量中,并保留元素顺序。列表有两个重要特点:

  1. 有顺序
  2. 可修改

和字符串不同,列表中的元素可以直接增加、删除和修改。

案例:维护课程名单 ​

需求:先保存一组课程名称,再支持按位置读取、增加课程、删除课程和调整顺序。接下来从最小可运行的列表开始。

列表使用 [] 创建,元素之间用逗号分隔。先把课程名称集中保存到一个变量中:

python
course_list = ["Python", "MySQL", "Linux"]
print(course_list)

列表可以保存任意数量的元素,也可以创建空列表。名单暂时没有内容时,就从空列表开始:

list.svg

提示

列表索引从 0 开始。第一个元素的索引是 0,不是 1。

python
course_list = []
print(course_list)

运行结果:

text
[]

列表可以保存不同类型的数据,也可以继续保存其他列表:

python
number_list = [1, 2, 3, 4, 5]
data_list = ["Python", 95.5, True, number_list]

print(data_list)

类型转换 ​

使用 list() 可以把字符串等可遍历内容转换成列表:

python
text = "Python"
char_list = list(text)

print(char_list)

运行结果:

text
['P', 'y', 't', 'h', 'o', 'n']

索引与切片 ​

如果需要读取名单中的某个人,或者只取出其中一段数据,就要按位置访问。列表属于序列类型,支持索引取值和切片。

索引访问 ​

列表中的每个元素都有一个位置编号,这个编号叫作 索引。索引从 0 开始,负数索引从末尾开始。

python
course_list = ["Python", "MySQL", "Linux"]

print(course_list[0])
print(course_list[-1])

运行结果:

text
Python
Linux

course_list[0] 取得第一个元素,course_list[-1] 取得最后一个元素。

提示

按索引取值时,如果索引超出范围,程序会抛出 IndexError。

切片访问 ​

切片就是按范围取出一部分数据。字符串、列表、元组这些序列都支持切片。

格式是 列表[start:stop:step],也就是 列表[起始位置:结束位置:步长]。

  • start:起始索引
  • stop:结束索引
  • step:步长,默认是 1
python
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,得到倒序结果

运行结果:

text
[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]

切片范围与越界 ​

如果要核对切片到底取到了哪些元素,可以结合下面的图和代码逐项观察。规则只有一句:切片包含起始位置,不包含结束位置。

列表双向索引与切片提取机制图

python
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,就能看到列表常用方法。

text
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() 最常用,建议先写熟这一组。

注意:列表的大部分方法都是就地修改,也就是直接改原列表。

Python 列表常用方法全景速查图

下面的连续示例展示同一个列表如何逐步发生变化,运行每一步时都要观察原列表的内容:

python
>>> 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() 可以在指定位置插入:

python
name_list = ["正心", "全栈", "编程"]

name_list.append("Python")
name_list.insert(0, "零基础")

print(name_list)

如果想把一个列表的内容加到另一个列表,要分清 append() 和 extend()。

python
teacher_list = ["老师 1", "老师 2"]
new_teachers = ["老师 3", "老师 4"]

teacher_list.append(new_teachers)
print(teacher_list)
python
teacher_list = ["老师 1", "老师 2"]
new_teachers = ["老师 3", "老师 4"]

for teacher in new_teachers:
    teacher_list.append(teacher)

print(teacher_list)
python
teacher_list = ["老师 1", "老师 2"]
new_teachers = ["老师 3", "老师 4"]

teacher_list.extend(new_teachers)
print(teacher_list)

append(new_teachers) 会把整个列表当成一个元素;extend(new_teachers) 会把其中的元素逐个追加到原列表。

append 和 extend 追加列表结果的对比图

元素删除 ​

需要移除课程时,先区分“按位置删除”和“按内容删除”。最常见的方法是 pop()、remove() 和 clear()。

python
>>> 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 按索引删除:

python
name_list = ["正心", "全栈", "编程"]
del name_list[1]

print(name_list)

元素修改与排序 ​

列表是可变类型,所以可以直接按位置改值,也可以整体逆序或排序。下面同时观察这三类操作对原列表的影响。

python
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()。

python
>>> 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

案例:办公室座位安排 ​

案例:员工与位置匹配 ​

需求:把员工和办公室位置一一对应起来。已知两个等长列表,需要使用相同索引取得对应数据。

python
name_list = ["邓永明", "廖德超", "张勇", "杨久林", "戴贵富", "秦代坤", "李元东", "田显余"]
site_list = [
    "1 号办公室 1 位置", "1 号办公室 2 位置", "1 号办公室 3 位置",
    "2 号办公室 1 位置", "2 号办公室 2 位置", "2 号办公室 3 位置",
    "3 号办公室 1 位置", "3 号办公室 2 位置",
]

# 把分配结果保存到这个列表
content = []

功能分析:

  1. 使用索引依次取得员工
  2. 使用相同索引取得对应的位置
  3. 把“位置 + 员工”组成一个新列表
  4. 将分配结果追加到 content

通过相同索引对应员工姓名和办公室位置的示意图

参考代码
python
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. 分别从 1 号、2 号办公室随机选取一名员工
  2. 将两名员工安排到 4 号办公室
  3. 最后打印调整后的座位表
python
import random

seating = [
    ["1 号办公室 1 位置", "戴贵富"],
    ["1 号办公室 2 位置", "田显余"],
    ["1 号办公室 3 位置", "李元东"],
    ["2 号办公室 1 位置", "廖德超"],
    ["2 号办公室 2 位置", "秦代坤"],
    ["2 号办公室 3 位置", "杨久林"],
    ["3 号办公室 1 位置", "邓永明"],
    ["3 号办公室 2 位置", "张勇"],
]

提示

可以先用 random 模块生成随机数:

python
# 导入标准库中的 random 模块
import random

random.randint(1, 10)

random.randint(a, b) 会返回 a 到 b 之间的随机整数,包含两端。

参考代码
python
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 个人要参加会议,如何随机安排座位,并确保一个位置只分配给一个人?

python
import random

# 有多少个人
name_list = [
    '邓永明', '廖德超', '张勇', '杨久林',
    '戴贵富', '秦代坤', '李元东', '田显余'
]

site_list = [
    '1 号办公室 1 位置', '1 号办公室 2 位置', '1 号办公室 3 位置',
    '2 号办公室 1 位置', '2 号办公室 2 位置', '2 号办公室 3 位置',
    '3 号办公室 1 位置', '3 号办公室 2 位置'
]

# 答案放这里
seating = []
参考代码
python
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)

二维列表 ​

如果座位表、成绩表或文字排版需要同时表示行和列,一个列表就不够了。列表里还可以继续放列表:外层列表表示多行,内层列表表示每一行,这种结构通常叫作二维列表。

python
classroom = [
    ["小明", "小红", "小刚"],
    ["小李", "小王", "小周"],
]

# 先取得第 1 行,再取得第 1 行中的第 2 个元素
print(classroom[0])
print(classroom[0][1])

运行结果:

text
['小明', '小红', '小刚']
小红

二维列表遍历 ​

读取二维列表时,要先确定需求:只关心每个元素,还是还需要知道行号和列号。对应有两种常见遍历方式:

python
classroom = [
    ["小明", "小红", "小刚"],
    ["小李", "小王", "小周"],
]

for row in classroom:
    for name in row:
        print(name, end=" ")
    print()
python
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()

只需要读取所有元素时,直接遍历更简单。需要知道行号、列号,或者要修改指定位置时,可以根据索引遍历。

案例:古诗词转置 ​

这个案例的重点是体会“行”和“列”的区别。

python
print("""横向打印效果:
千山鸟飞绝
万径人踪灭
孤舟蓑笠翁
独钓寒江雪

纵向打印效果:
千万孤独
山径舟钓
鸟人蓑寒
飞踪笠江
绝灭翁雪""")

从一维列表到二维列表的行列索引定位图

python
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()

二维列表按行列访问并进行古诗词转置的示意图

小结 ​

本节先回答五个问题:

  1. 为什么使用列表?因为它能按顺序保存一组可修改的数据
  2. 如何取得数据?索引取得单个元素,切片取得一段元素
  3. 如何批量增加数据?append() 追加一个元素,extend() 逐个追加另一个列表的元素
  4. 如何删除和查询数据?pop() 按位置删除,remove() 按内容删除,再配合 index()、count() 和 len() 查询
  5. 如何处理行列数据?使用二维列表,并分清行索引和列索引

练习 ​

  1. 自己写一个课程名称列表,分别练习 append()、insert()、pop()。
  2. 写一段代码,取出列表的前 3 个元素、最后 2 个元素、倒序结果。
  3. 把一个二维列表按行打印一次,再按列打印一次。