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 并没有复制列表,只是给同一个列表又贴了一个标签。想象一个盒子上贴了 a、b 两张标签——不管通过哪张标签往盒子里放东西,另一张标签看到的都是同一个盒子。
想要真正独立的副本,明确地复制一份:
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
本章小结
- 列表
[]存一串数据;索引、切片、in、len()规则与字符串完全一致 - 列表可变:改
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