跳到主要内容

4.1 列表 list

先看一个用现有知识解决不了的问题:存全班 40 个人的成绩

score1 = 90
score2 = 85
score3 = 77
# ……还要写 37 个?算平均分难道要 score1 + score2 + ... + score40?

显然行不通。我们需要一种能把一串数据装进一个变量的东西——这就是列表(list),Python 中最常用的数据结构,没有之一。

创建列表

方括号 [] 把数据包起来,元素之间用逗号隔开:

scores = [90, 85, 77, 62]

拆开看:

scores = [ 90 , 85 , 77 , 62 ]
↑ ↑ ↑
变量名 方括号 每个数据叫一个"元素",用逗号分隔

列表对内容毫无限制——任意类型、任意数量,甚至混着放:

fruits = ["苹果", "香蕉", "橘子"] # 一串字符串
mixed = ["小明", 18, 1.75, True] # 混合类型也合法(但实际少用)
empty = [] # 空列表:先建个空的,后面往里加

len() 对列表同样适用,返回元素个数:

print(len(scores)) # 4
print(len(empty)) # 0

访问元素:索引

好消息:列表的索引和切片规则与 2.3 节的字符串一模一样——从 0 开始、负数倒着数、切片含头不含尾。字符串学扎实了,这里零成本:

fruits = [ "苹果", "香蕉", "橘子", "西瓜" ]
正着数: 0 1 2 3
倒着数: -4 -3 -2 -1
fruits = ["苹果", "香蕉", "橘子", "西瓜"]

print(fruits[0]) # 苹果 第 1 个元素,索引是 0
print(fruits[-1]) # 西瓜 最后一个
print(fruits[1:3]) # ['香蕉', '橘子'] 切片,含头不含尾,结果还是列表
print(fruits[:2]) # ['苹果', '香蕉']

越界报错也是老朋友:

print(fruits[4]) # ❌ IndexError: list index out of range
# 4 个元素,最大索引是 3

关键区别:列表是可变的

还记得 2.3 节字符串"不可变"、s[0] = "H" 会报错吗?列表恰恰相反,它是可变的——增、删、改都直接作用在原列表上。这是两者最本质的区别。

改:按索引赋值

fruits = ["苹果", "香蕉", "橘子"]
fruits[0] = "草莓"
print(fruits) # ['草莓', '香蕉', '橘子'] 原列表真的变了

增:append 和 insert

append()末尾追加一个元素,是列表所有方法里使用频率最高的:

fruits = ["苹果", "香蕉"]
fruits.append("西瓜")
print(fruits) # ['苹果', '香蕉', '西瓜']

⚠️ 大坑:append 直接修改原列表,返回值是 None,千万别赋值回去!

fruits = fruits.append("桃子") # ❌ 灾难写法
print(fruits) # None !整个列表没了

对比 2.3 节:字符串方法不改原串、返回新串,所以要 s = s.upper();列表方法直接改原列表、返回 None,所以不能接赋值。两种数据的方法行为正好相反,这里最容易搞混,务必分清。

想插在中间用 insert(位置, 元素)

fruits = ["苹果", "香蕉"]
fruits.insert(1, "芒果") # 插到索引 1 的位置,原来的元素往后挪
print(fruits) # ['苹果', '芒果', '香蕉']

删:remove、pop、del

三种删法,按场景选:

fruits = ["苹果", "香蕉", "橘子", "香蕉"]

# ① remove(值):知道"删什么",按值删。只删第一个匹配的
fruits.remove("香蕉")
print(fruits) # ['苹果', '橘子', '香蕉'] ← 第二个香蕉还在

# ② pop(索引):知道"删哪个位置",删掉并把该元素返回给你
last = fruits.pop() # 不写索引默认删最后一个
print(last) # 香蕉 ← 被删的元素还能接着用
first = fruits.pop(0) # 删索引 0
print(first) # 苹果

# ③ del 语句:按位置删,不需要拿到被删的值
fruits = ["苹果", "香蕉", "橘子"]
del fruits[1]
print(fruits) # ['苹果', '橘子']

remove 一个不存在的值会报错:

fruits.remove("榴莲") # ❌ ValueError: list.remove(x): x not in list

不确定在不在,先用 in 判断(下面就讲)。

遍历列表

第 3 章学的 for 循环,最主要的用武之地就是列表——把列表里的元素挨个拿出来处理

scores = [90, 85, 77, 62]

for s in scores:
print(f"成绩:{s}")

执行过程:第 1 轮 s 是 90,第 2 轮是 85……列表有几个元素就循环几轮,不用管长度,也永远不会越界。

如果既要元素又要序号,用 enumerate()(枚举):

fruits = ["苹果", "香蕉", "橘子"]

for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
# 1. 苹果
# 2. 香蕉
# 3. 橘子

start=1 表示序号从 1 开始数(默认从 0)。for i, fruit in ... 一次接两个值的写法,下一节元组会解释它的原理。

常用操作一览

查询统计类(都不改变列表):

scores = [90, 85, 77, 62, 85]

print(max(scores)) # 90 最大值
print(min(scores)) # 62 最小值
print(sum(scores)) # 399 求和
print(85 in scores) # True 是否存在(和字符串的 in 一样)
print(scores.count(85)) # 2 85 出现了几次
print(scores.index(77)) # 2 77 在哪个位置(不存在会 ValueError)

平均分的经典组合拳:sum(scores) / len(scores)

排序类(直接修改原列表):

scores = [90, 85, 77, 62, 85]

scores.sort() # 从小到大
print(scores) # [62, 77, 85, 85, 90]

scores.sort(reverse=True) # 从大到小
print(scores) # [90, 85, 85, 77, 62]

scores.reverse() # 原地掉个头(不是排序,只是首尾翻转)

⚠️ sort() 和 append 一样返回 None,scores = scores.sort() 又会把列表变没。如果想保留原列表、拿到一个排好序的新列表,用内置函数 sorted()

scores = [90, 85, 77]
ordered = sorted(scores) # 新列表
print(ordered) # [77, 85, 90]
print(scores) # [90, 85, 77] 原列表没动

记法:lst.sort() 是"就地整理",sorted(lst) 是"复印一份再整理"。

隐藏大坑:两个变量共享同一个列表

2.1 节做过这个实验:

a = 10
b = a
a = 20
print(b) # 10,b 不受影响

数字是这样。但把它换成列表,结果会颠覆你的预期:

a = [1, 2, 3]
b = a
a.append(4)
print(b) # [1, 2, 3, 4] ??b 也跟着变了!

原因:b = a 并没有复制列表,只是给同一个列表又贴了一个标签。想象一个盒子上贴了 ab 两张标签——不管通过哪张标签往盒子里放东西,另一张标签看到的都是同一个盒子。

想要真正独立的副本,明确地复制一份:

a = [1, 2, 3]
b = a.copy() # 或者写 b = a[:],切片复制,效果相同
a.append(4)
print(a) # [1, 2, 3, 4]
print(b) # [1, 2, 3] 这回互不影响了

现在记住现象和解法就够了:"列表赋值给新变量 = 贴标签,copy() = 真复制"。背后的原理(引用)到第 5 章变量作用域附近会更清楚。

综合例子:成绩统计器

把本节知识串成一个完整程序,新建 score_stat.py

# score_stat.py — 录入成绩并统计
scores = [] # 先建空列表

for i in range(5): # 循环录入 5 次
s = int(input(f"请输入第 {i + 1} 个成绩:"))
scores.append(s) # 每输入一个就追加进列表

print(f"最高分:{max(scores)}")
print(f"最低分:{min(scores)}")
print(f"平均分:{sum(scores) / len(scores):.1f}")

passed = 0 # 及格人数计数器
for s in scores:
if s >= 60:
passed += 1 # 2.2 节的 += 在这里发光
print(f"及格人数:{passed}")

"空列表 + 循环 append" 是收集数据的万能套路,"变量清零 + 循环 +=" 是计数的万能套路——这两个模式后面项目里会反复出现。

本节报错速查

报错人话翻译本节的触发场景
IndexError编号越界索引 ≥ len(lst)
ValueError值不存在remove()/index() 找不到目标值
TypeError类型不对sum(["a", "b"]) 这类对文本做数学运算

还有一个"不报错的错":lst = lst.append(x) / lst = lst.sort() 把列表变成 None——一旦后面出现 'NoneType' object ... 之类的报错,回头查查是不是干了这件事。

练习

新建 practice41.py 完成:

1. 列表 nums = [3, 8, 1, 9, 4]:把 8 改成 80,删掉 1,末尾追加 100,从大到小排序,打印结果。

2. 不运行,先猜输出,再验证:

nums = [5, 2, 9]
print(nums[-1] + nums[0])
print(sorted(nums))
print(nums)

a = [1, 2]
b = a
b.append(3)
print(a)

3. 下面的程序想把成绩从小到大排序后打印,但输出是 None,找出原因修好它:

scores = [88, 61, 95, 73]
scores = scores.sort()
print(scores)

4.(挑战)找出列表 [12, 45, 3, 78, 45, 9] 中第二大的数。提示:排序后取索引。

点击查看答案
# 1
nums = [3, 8, 1, 9, 4]
nums[1] = 80
nums.remove(1)
nums.append(100)
nums.sort(reverse=True)
print(nums) # [100, 80, 9, 4, 3]

第 2 题:

  • nums[-1] + nums[0]14(9 + 5)
  • sorted(nums)[2, 5, 9](新列表,从小到大)
  • print(nums)[5, 2, 9](sorted 不动原列表)
  • print(a)[1, 2, 3](a、b 是同一个列表的两张标签,b 追加 a 也能看到)

第 3 题: sort() 直接修改原列表并返回 None,再赋值回去列表就丢了。两种改法任选:

scores = [88, 61, 95, 73]
scores.sort() # 改法一:不要赋值
print(scores)

scores = sorted(scores) # 改法二:用 sorted(),它返回新列表
# 4
nums = [12, 45, 3, 78, 45, 9]
nums.sort(reverse=True)
print(nums[1]) # 45

本章小结

  • 列表 [] 存一串数据;索引、切片、inlen() 规则与字符串完全一致
  • 列表可变:改 lst[i] = x,增 append/insert,删 remove(按值)/pop(按位删并返回)/del
  • 列表方法直接改原列表、返回 None——lst = lst.append(x)lst = lst.sort() 是毁灭性写法
  • sort() 就地排序,sorted() 复印再排序
  • b = a 只是贴标签,两个名字共享一个列表;要独立副本用 a.copy()
  • 两大万能套路:空列表 + 循环 append 收集数据;计数器 + 循环 += 统计

下一节:4.2 元组 tuple