Python list 和 tuple:从"购物车"到"保险箱"的思维跃迁
引言:为什么需要"容器"?
想象你是一家餐厅的店长,每天要记录顾客的订单。你可以用便签纸一张张写,但这样查找、修改、统计都非常麻烦。更好的办法是准备一个订单本——按顺序记录,随时翻看,随时修改。
在编程世界里,list 和 tuple 就是这样的"容器"。它们用来按顺序存放多个数据,是 Python 中最基础、最常用的数据结构。
但这两个容器性格迥异:list 像一本活页笔记本,可以随时增删改查;tuple 像一本装订好的书,一旦写完就不能再改。理解它们的区别,是写出安全、高效代码的关键。
一、list:你的"活页笔记本"
1.1 什么是 list?
list(列表)是 Python 内置的有序、可变的数据集合。用方括号 [] 表示,元素之间用逗号分隔。
# 记录班里同学的名字
classmates = ['Michael', 'Bob', 'Tracy']
# 记录今天的待办事项
todo_list = ['写代码', '开会', '健身', '看书']
# 记录商品价格
prices = [9.9, 19.8, 29.9, 99.0]生活化理解:list 就像你手机里的购物车——商品按加入顺序排列,可以随时添加新商品、删除不想要的、修改数量。
1.2 创建 list 的几种方式
# 方式1:直接列出元素
fruits = ['苹果', '香蕉', '橙子']
# 方式2:空 list,后续添加
shopping_cart = []
# 方式3:用 list() 转换其他数据
numbers = list(range(5)) # [0, 1, 2, 3, 4]
chars = list('hello') # ['h', 'e', 'l', 'l', 'o']1.3 访问元素:索引的艺术
list 中的每个元素都有一个位置编号,叫做索引(index)。
classmates = ['Michael', 'Bob', 'Tracy']
# 0 1 2 ← 正索引
# -3 -2 -1 ← 负索引
print(classmates[0]) # Michael(第1个)
print(classmates[1]) # Bob(第2个)
print(classmates[-1]) # Tracy(倒数第1个)
print(classmates[-2]) # Bob(倒数第2个)关键规则:
- 正索引从 0 开始(不是 1!)
- 负索引从 -1 开始(倒数第一个)
- 最后一个元素的正索引 =
len(list) - 1
为什么从 0 开始? 这是计算机科学的传统。想象楼层:地面层是 0 楼,往上是 1、2、3...,往下是 -1、-2...
1.4 越界:常见的 IndexError
classmates = ['Michael', 'Bob', 'Tracy']
print(classmates[3]) # 报错!只有 0,1,2 三个索引
# IndexError: list index out of range生活化理解:你的购物车只有 3 件商品,你却要看第 4 件——系统只能报错。
防御性写法:
if len(classmates) > 3:
print(classmates[3])
else:
print('没有第4个元素')1.5 len():数一数有多少个
classmates = ['Michael', 'Bob', 'Tracy']
print(len(classmates)) # 3
empty_list = []
print(len(empty_list)) # 0len() 是 Python 的通用函数,字符串、list、tuple、字典等都可以用。
1.6 添加元素:append() 和 insert()
append():在末尾追加
classmates = ['Michael', 'Bob', 'Tracy']
classmates.append('Adam')
print(classmates) # ['Michael', 'Bob', 'Tracy', 'Adam']insert():在指定位置插入
classmates = ['Michael', 'Bob', 'Tracy']
classmates.insert(1, 'Jack') # 在索引1的位置插入
print(classmates) # ['Michael', 'Jack', 'Bob', 'Tracy']生活化理解:
append()像排队时排在最后insert()像插队——后面的人依次后移
1.7 删除元素:pop() 和 remove()
pop():删除并返回指定位置的元素
classmates = ['Michael', 'Bob', 'Tracy']
# 删除末尾元素
last = classmates.pop()
print(last) # Tracy
print(classmates) # ['Michael', 'Bob']
# 删除指定位置
second = classmates.pop(1)
print(second) # Bob
print(classmates) # ['Michael']remove():删除指定值的元素
fruits = ['苹果', '香蕉', '橙子', '香蕉']
fruits.remove('香蕉') # 只删除第一个匹配的
print(fruits) # ['苹果', '橙子', '香蕉']del 语句:按索引删除
classmates = ['Michael', 'Bob', 'Tracy']
del classmates[1]
print(classmates) # ['Michael', 'Tracy']1.8 修改元素:直接赋值
classmates = ['Michael', 'Bob', 'Tracy']
classmates[1] = 'Sarah'
print(classmates) # ['Michael', 'Sarah', 'Tracy']1.9 list 的"混搭"与嵌套
元素类型可以不同:
mixed = ['Apple', 123, True, 3.14, None]list 可以嵌套 list:
s = ['python', 'java', ['asp', 'php'], 'scheme']
print(len(s)) # 4(不是5!)
print(s[2]) # ['asp', 'php']
print(s[2][1]) # 'php'生活化理解:就像书架上的一个格子,里面可以放一本书(普通元素),也可以放一个小盒子,盒子里再装几本书(嵌套 list)。
二维表格的表示:
# 3行3列的成绩表
scores = [
[85, 92, 78], # 第1个学生的3科成绩
[90, 88, 95], # 第2个学生
[76, 85, 82] # 第3个学生
]
print(scores[0][1]) # 92(第1个学生的第2科)1.10 list 的常用操作速查
| 操作 | 方法 | 示例 | 结果 |
|---|---|---|---|
| 添加 | append(x) | l.append(4) | 末尾添加 |
| 插入 | insert(i, x) | l.insert(0, 'a') | 索引0处插入 |
| 删除末尾 | pop() | l.pop() | 删除并返回最后一个 |
| 删除指定 | pop(i) | l.pop(0) | 删除并返回索引0的元素 |
| 按值删除 | remove(x) | l.remove('a') | 删除第一个匹配项 |
| 修改 | l[i] = x | l[0] = 'b' | 修改索引0的值 |
| 查找索引 | index(x) | l.index('a') | 返回首次出现的索引 |
| 统计次数 | count(x) | l.count('a') | 统计出现次数 |
| 排序 | sort() | l.sort() | 原地排序 |
| 反转 | reverse() | l.reverse() | 原地反转 |
| 切片 | l[1:3] | l[1:3] | 获取子列表 |
二、tuple:你的"装订好的书"
2.1 什么是 tuple?
tuple(元组)是有序、不可变的数据集合。用圆括号 () 表示。
# 定义一个 tuple
classmates = ('Michael', 'Bob', 'Tracy')
# 坐标点
point = (3, 5)
# 数据库记录
user = ('张三', 25, '北京', '工程师')生活化理解:tuple 就像打印出来的纸质书——内容固定,不能涂改。你可以阅读(访问元素),但不能撕掉一页或插入一页。
2.2 tuple 的访问方式
与 list 完全相同:
classmates = ('Michael', 'Bob', 'Tracy')
print(classmates[0]) # Michael
print(classmates[-1]) # Tracy
print(len(classmates)) # 32.3 为什么需要 tuple?
你可能会问:list 已经很好用了,为什么还要 tuple?
原因 1:安全性
# 用 tuple 存储不应该被修改的数据
birth_date = (1990, 5, 15) # 出生日期,永远不变
# 如果用 list,不小心就会被修改
birth_date_list = [1990, 5, 15]
birth_date_list[0] = 2000 # 被篡改了!原因 2:性能 tuple 比 list 更轻量,创建和访问速度更快。
原因 3:字典的 key
# tuple 可以作为字典的 key,list 不行
location_map = {
(39.9, 116.4): '北京',
(31.2, 121.5): '上海'
}2.4 tuple 的"陷阱":单元素 tuple
# ❌ 错误:这不是 tuple,是数字 1
t = (1)
print(type(t)) # <class 'int'>
# ✅ 正确:单元素 tuple 必须加逗号
t = (1,)
print(type(t)) # <class 'tuple'>为什么? 因为 () 在数学运算中也有意义,比如 (1 + 2) * 3。Python 无法区分 (1) 是"括号里的 1"还是"单元素 tuple",所以规定:单元素 tuple 必须加逗号。
2.5 "可变的" tuple:指向不变
这是最容易迷惑的地方:
t = ('a', 'b', ['A', 'B'])
print(t) # ('a', 'b', ['A', 'B'])
# 修改 tuple 里的 list
t[2][0] = 'X'
t[2][1] = 'Y'
print(t) # ('a', 'b', ['X', 'Y'])不是 tuple 不能变吗?怎么变了?
生活化理解:
- tuple 像是一个相框,里面放了 3 张照片:'a'、'b' 和一个 list
- 相框本身不能换照片(tuple 元素不能重新赋值)
- 但第 3 张照片是一个相册(list),相册里的照片可以换
"不变"的真正含义:tuple 的每个元素指向的对象不能变,但对象本身的内容可以变。
# 画图理解
# t[0] → 'a'(不能改成指向 'c')
# t[1] → 'b'(不能改成指向 'd')
# t[2] → ['A', 'B'](不能改成指向其他 list,但这个 list 内容可以改)2.6 tuple 的常用操作
t = (1, 2, 3, 2, 2)
# 访问
print(t[0]) # 1
print(t[-1]) # 2
# 统计
print(t.count(2)) # 3
print(t.index(3)) # 2(首次出现的索引)
# 连接
t1 = (1, 2)
t2 = (3, 4)
print(t1 + t2) # (1, 2, 3, 4)
# 重复
print(t1 * 3) # (1, 2, 1, 2, 1, 2)三、list vs tuple:如何选择?
| 对比维度 | list | tuple |
|---|---|---|
| 可变性 | 可变(增删改) | 不可变 |
| 语法 | [] | () |
| 性能 | 较慢 | 较快 |
| 内存占用 | 较大 | 较小 |
| 字典 key | 不能 | 能 |
| 适用场景 | 数据会变化 | 数据固定不变 |
选择原则:
- 数据需要修改?→ 用 list
- 数据固定不变?→ 用 tuple
- 要作为字典 key?→ 必须用 tuple
- 不确定?→ 优先 tuple(更安全)
实际应用场景:
# 用 list:购物车(会增删商品)
shopping_cart = ['苹果', '香蕉']
# 用 tuple:地理坐标(固定不变)
beijing = (39.9, 116.4)
# 用 list:待办事项(会增删改)
todo_list = ['写报告', '开会']
# 用 tuple:函数返回多个值
def get_user_info():
return ('张三', 25, '北京') # 返回 tuple
name, age, city = get_user_info() # 解包四、常见误区与陷阱
4.1 误区一:索引从 1 开始
# 很多初学者以为
classmates = ['Michael', 'Bob', 'Tracy']
print(classmates[1]) # 以为是 Michael,实际是 Bob
# 正确:第 1 个元素的索引是 0
print(classmates[0]) # Michael4.2 误区二:append() 的返回值
# ❌ 错误:append() 返回 None,不是新 list
l = [1, 2, 3]
new_l = l.append(4)
print(new_l) # None
print(l) # [1, 2, 3, 4]
# ✅ 正确:append() 是原地修改
l = [1, 2, 3]
l.append(4)
print(l) # [1, 2, 3, 4]4.3 误区三:单元素 tuple 忘记逗号
# 错误
t = (1) # 这是数字 1,不是 tuple
# 正确
t = (1,) # 这才是 tuple4.4 误区四:tuple 完全不能变
# 错误理解:tuple 里的所有内容都不能变
t = (1, 2, [3, 4])
t[2].append(5) # 这实际上是可以的!
print(t) # (1, 2, [3, 4, 5])
# 正确理解:tuple 的"指向"不能变,但指向的对象内容可以变
# t[2] 不能指向另一个 list,但这个 list 本身可以修改4.5 误区五:复制 list 的陷阱
# ❌ 错误:这是引用,不是复制
l1 = [1, 2, 3]
l2 = l1
l2.append(4)
print(l1) # [1, 2, 3, 4](l1 也被改了!)
# ✅ 正确:复制 list
l1 = [1, 2, 3]
# 方法1:切片
l2 = l1[:]
# 方法2:list()
l2 = list(l1)
# 方法3:copy 模块(浅拷贝)
import copy
l2 = copy.copy(l1)
# 深拷贝(嵌套 list 时)
l3 = copy.deepcopy(l1)五、实战案例
案例 1:学生成绩管理
# 用嵌套 list 存储学生成绩
students = [
['张三', [85, 92, 78]],
['李四', [90, 88, 95]],
['王五', [76, 85, 82]]
]
# 打印所有学生的平均分
for name, scores in students:
avg = sum(scores) / len(scores)
print(f'{name} 的平均分:{avg:.1f}')
# 输出:
# 张三 的平均分:85.0
# 李四 的平均分:91.0
# 王五 的平均分:81.0案例 2:简单的队列系统
# 用 list 模拟排队
queue = []
# 入队
queue.append('顾客A')
queue.append('顾客B')
queue.append('顾客C')
print(f'当前队列:{queue}') # ['顾客A', '顾客B', '顾客C']
# 出队(先进先出)
first = queue.pop(0)
print(f'服务:{first}') # 顾客A
print(f'剩余队列:{queue}') # ['顾客B', '顾客C']案例 3:坐标系统
# 用 tuple 存储坐标点
points = [(0, 0), (1, 1), (2, 4), (3, 9)]
# 计算每个点到原点的距离
import math
for x, y in points:
distance = math.sqrt(x**2 + y**2)
print(f'点({x}, {y}) 到原点的距离:{distance:.2f}')六、动手练习
练习 1:用索引取出下面 list 的指定元素
L = [
['Apple', 'Google', 'Microsoft'],
['Java', 'Python', 'Ruby', 'PHP'],
['Adam', 'Bart', 'Bob']
]
# 打印 Apple
print(L[0][0])
# 打印 Python
print(L[1][1])
# 打印 Bob
print(L[2][2])练习 2:判断哪些是 tuple
a = () # tuple(空 tuple)
b = (1) # int(不是 tuple!)
c = [2] # list
d = (3,) # tuple(注意逗号)
e = (4,5,6) # tuple总结
| 概念 | 核心要点 |
|---|---|
| list | 有序、可变、用 []、索引从 0 开始 |
| tuple | 有序、不可变、用 ()、单元素要加逗号 |
| 索引 | 正索引 0 开始,负索引 -1 开始 |
| append() | 原地修改,返回 None |
| tuple 的"不变" | 指向不变,但指向的对象内容可以变 |
| 选择原则 | 数据可变选 list,固定选 tuple |
记住:list 是你的活页笔记本,tuple 是你的保险箱。根据数据是否需要修改来选择,不确定时优先 tuple(更安全)。
本文基于廖雪峰 Python 教程重新撰写,补充了大量背景知识、生活化类比和实战场景。推荐继续阅读:条件判断