9 minutes
列表与元组详解
前面我们学习了字符串,它是 Python 中的一种序列类型。今天我们将学习另外两种非常重要的序列类型——列表和元组。它们可以存储多个元素,是 Python 编程中使用最频繁的数据结构之一。
列表(List)
列表是 Python 中最灵活、最常用的序列类型。它是一个有序、可变的容器,可以存储任意类型的元素。
创建列表
# 创建列表的几种方式
empty_list = [] # 空列表
numbers = [1, 2, 3, 4, 5] # 整数列表
mixed = [1, "hello", 3.14, True] # 混合类型
nested = [[1, 2], [3, 4], [5, 6]] # 嵌套列表(二维)
# 使用 list() 构造
chars = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']
range_list = list(range(5)) # [0, 1, 2, 3, 4]
# 列表推导式(创建列表的简洁方式)
squares = [x ** 2 for x in range(10)] # [0, 1, 4, 9, ..., 81]
evens = [x for x in range(20) if x % 2 == 0] # [0, 2, 4, ..., 18]
print(f"numbers: {numbers}")
print(f"mixed: {mixed}")
print(f"nested: {nested}")
print(f"chars: {chars}")
访问列表元素
列表的索引和切片操作与字符串完全一致:
numbers = [10, 20, 30, 40, 50]
# 正索引(从 0 开始)
print(numbers[0]) # 10
print(numbers[2]) # 30
print(numbers[4]) # 50
# 负索引(从 -1 开始)
print(numbers[-1]) # 50(最后一个)
print(numbers[-3]) # 30(倒数第三个)
# 切片
print(numbers[1:4]) # [20, 30, 40]
print(numbers[:3]) # [10, 20, 30]
print(numbers[::2]) # [10, 30, 50]
print(numbers[::-1]) # [50, 40, 30, 20, 10](反转)
# 切片返回新列表,不影响原列表
original = [1, 2, 3, 4, 5]
sliced = original[1:4]
print(sliced) # [2, 3, 4]
print(original) # [1, 2, 3, 4, 5](不变)
# 切片也可以用于赋值
original[1:4] = [20, 30, 40]
print(original) # [1, 20, 30, 40, 5]
修改列表
列表与字符串最大的区别是:列表可变:
# 修改单个元素
fruits = ["苹果", "香蕉", "橙子"]
fruits[1] = "葡萄"
print(fruits) # ['苹果', '葡萄', '橙子']
# 修改切片
fruits[1:3] = ["荔枝", "芒果"]
print(fruits) # ['苹果', '荔枝', '芒果']
# 添加元素
fruits.append("西瓜") # 在末尾添加一个元素
print(fruits) # ['苹果', '荔枝', '芒果', '西瓜']
fruits.insert(1, "草莓") # 在指定位置插入
print(fruits) # ['苹果', '草莓', '荔枝', '芒果', '西瓜']
fruits.extend(["桃子", "樱桃"]) # 扩展列表(添加多个元素)
print(fruits) # ['苹果', '草莓', '荔枝', '芒果', '西瓜', '桃子', '樱桃']
# 删除元素
fruits.remove("荔枝") # 删除第一个匹配的元素
print(fruits) # ['苹果', '草莓', '荔枝', '芒果', '西瓜', '桃子', '樱桃']
# 不对,"荔枝"已经被删除了,应该写一个新的示例
fruits = ["苹果", "香蕉", "橙子", "葡萄"]
popped = fruits.pop() # 弹出并返回最后一个元素
print(popped) # 葡萄
print(fruits) # ['苹果', '香蕉', '橙子']
popped_at = fruits.pop(1) # 弹出指定位置的元素
print(popped_at) # 香蕉
print(fruits) # ['苹果', '橙子']
del fruits[0] # 删除指定位置的元素
print(fruits) # ['橙子']
fruits.clear() # 清空列表
print(fruits) # []
列表方法详解
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# sort() - 原地排序
numbers.sort()
print(numbers) # [1, 1, 2, 3, 4, 5, 6, 9]
# sort(reverse=True) - 降序排序
numbers.sort(reverse=True)
print(numbers) # [9, 6, 5, 4, 3, 2, 1, 1]
# sorted() - 返回新的排序列表(不修改原列表)
original = [3, 1, 4, 1, 5]
sorted_nums = sorted(original)
print(sorted_nums) # [1, 1, 3, 4, 5]
print(original) # [3, 1, 4, 1, 5](不变)
# reverse() - 原地反转
numbers.reverse()
print(numbers) # [1, 1, 2, 3, 4, 5, 6, 9]
# index() - 查找元素位置
fruits = ["苹果", "香蕉", "橙子", "香蕉"]
print(fruits.index("香蕉")) # 1(第一次出现的位置)
print(fruits.index("香蕉", 2)) # 3(从索引2开始找)
# count() - 统计出现次数
print(fruits.count("香蕉")) # 2
# copy() - 复制列表
a = [1, 2, 3]
b = a.copy() # 或者 a[:]
b.append(4)
print(a) # [1, 2, 3](不变)
print(b) # [1, 2, 3, 4]
# 注意直接赋值只是引用
c = a
c.append(5)
print(a) # [1, 2, 3, 5](a 也被修改了!)
列表操作总结
| 操作 | 方法/语法 | 说明 |
|---|---|---|
| 追加 | list.append(x) |
末尾添加一个元素 |
| 扩展 | list.extend(iter) |
末尾添加多个元素 |
| 插入 | list.insert(i, x) |
在位置 i 插入 x |
| 删除 | list.remove(x) |
删除第一个匹配的 x |
| 弹出 | list.pop(i) |
弹出并返回位置 i 的元素 |
| 清空 | list.clear() |
清空所有元素 |
| 索引 | list.index(x) |
返回 x 第一次出现的位置 |
| 计数 | list.count(x) |
统计 x 出现的次数 |
| 排序 | list.sort() |
原地排序 |
| 反转 | list.reverse() |
原地反转 |
| 复制 | list.copy() |
复制列表 |
| 长度 | len(list) |
获取列表长度 |
| 成员 | x in list |
检查 x 是否在列表中 |
列表推导式
列表推导式是 Python 最优雅的特性之一,它能用一行代码生成复杂的列表:
# 基本形式:[expression for item in iterable]
squares = [x ** 2 for x in range(5)]
print(squares) # [0, 1, 4, 9, 16]
# 带条件筛选
evens = [x for x in range(20) if x % 2 == 0]
print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# 带条件表达式的推导式(注意位置不同)
labels = ["偶数" if x % 2 == 0 else "奇数" for x in range(5)]
print(labels) # ['偶数', '奇数', '偶数', '奇数', '偶数']
# 双重循环
pairs = [(x, y) for x in [1, 2] for y in [3, 4]]
print(pairs) # [(1, 3), (1, 4), (2, 3), (2, 4)]
# 展平嵌套列表
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [num for row in matrix for num in row]
print(flat) # [1, 2, 3, 4, 5, 6]
# 结合函数
words = ["hello", "world", "python"]
upper_words = [w.upper() for w in words]
print(upper_words) # ['HELLO', 'WORLD', 'PYTHON']
# 文件处理中的实际用途
lines = [" line1 ", "line2", " line3 "]
cleaned = [line.strip() for line in lines if line.strip()]
print(cleaned) # ['line1', 'line2', 'line3']
嵌套列表
嵌套列表可以表示矩阵、表格等二维或多维数据:
# 创建 3x3 矩阵
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# 访问元素
print(matrix[0][0]) # 1(第一行第一列)
print(matrix[1][2]) # 6(第二行第三列)
print(matrix[2]) # [7, 8, 9](第三行)
# 遍历嵌套列表
for row in matrix:
for element in row:
print(element, end=" ")
print()
# 输出:
# 1 2 3
# 4 5 6
# 7 8 9
# 列表推导式创建矩阵
size = 3
matrix2 = [[i * size + j + 1 for j in range(size)] for i in range(size)]
print(matrix2) # [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 转置矩阵
transposed = [[row[i] for row in matrix] for i in range(3)]
print(transposed) # [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
列表的深拷贝与浅拷贝
import copy
# 浅拷贝:只复制外层
nested = [[1, 2], [3, 4]]
shallow = nested.copy()
shallow[0][0] = 99
print(nested) # [[99, 2], [3, 4]](被改了!)
print(shallow) # [[99, 2], [3, 4]]
# 深拷贝:完全独立副本
nested2 = [[1, 2], [3, 4]]
deep = copy.deepcopy(nested2)
deep[0][0] = 99
print(nested2) # [[1, 2], [3, 4]](没变)
print(deep) # [[99, 2], [3, 4]]
元组(Tuple)
元组与列表非常相似,但有一个关键区别:元组是不可变的。创建后不能修改其内容。
创建元组
# 创建元组的几种方式
empty_tuple = () # 空元组
single_tuple = (1,) # 单个元素需要加逗号(重要!)
not_a_tuple = (1) # 这是整数 1,不是元组
numbers = (1, 2, 3, 4, 5) # 整数元组
mixed = (1, "hello", 3.14, True) # 混合类型
nested = ((1, 2), (3, 4), (5, 6)) # 嵌套元组
# 省略括号(元组由逗号定义)
point = 3, 5
print(point) # (3, 5)
print(type(point)) # <class 'tuple'>
# 使用 tuple() 构造
chars = tuple("Python") # ('P', 'y', 't', 'h', 'o', 'n')
range_tuple = tuple(range(5)) # (0, 1, 2, 3, 4)
print(f"numbers: {numbers}")
print(f"point: {point}")
print(f"chars: {chars}")
元组的访问
元组支持与列表相同的索引和切片操作:
colors = ("red", "green", "blue", "yellow")
# 索引
print(colors[0]) # red
print(colors[-1]) # yellow
# 切片
print(colors[1:3]) # ('green', 'blue')
print(colors[::-1]) # ('yellow', 'blue', 'green', 'red')
# 成员检查
print("green" in colors) # True
print("black" in colors) # False
# 遍历
for color in colors:
print(color, end=" ") # red green blue yellow
print()
元组的不可变性
# 元组不可变
point = (3, 5)
# point[0] = 10 # TypeError: 'tuple' object does not support item assignment
# 但如果元组中包含可变对象,那个对象本身可以变化
nested = ([1, 2], [3, 4])
nested[0].append(3)
print(nested) # ([1, 2, 3], [3, 4])
# 元组的方法非常有限
print(point.count(3)) # 1(统计元素出现次数)
print(point.index(5)) # 1(查找元素位置)
元组解包(Unpacking)
元组解包是 Python 非常实用的特性:
# 基本解包
point = (3, 5)
x, y = point
print(f"x={x}, y={y}") # x=3, y=5
# 交换变量(本质是元组解包)
a, b = 10, 20
a, b = b, a
print(f"a={a}, b={b}") # a=20, b=10
# 用 * 收集剩余元素
first, *middle, last = range(10)
print(first) # 0
print(middle) # [1, 2, 3, 4, 5, 6, 7, 8]
print(last) # 9
# 函数返回多个值(实际上是元组)
def min_max(numbers):
return min(numbers), max(numbers)
result = min_max([3, 1, 4, 1, 5, 9])
print(result) # (1, 9)
print(type(result)) # <class 'tuple'>
# 解包函数返回值
low, high = min_max([3, 1, 4, 1, 5, 9])
print(f"最小值: {low}, 最大值: {high}")
当元组遇到列表推导式
# 元组本身没有推导式语法,但可以用生成器表达式
source = [1, 2, 3, 4, 5]
# 生成器表达式(用小括号)
gen = (x ** 2 for x in source)
print(tuple(gen)) # (1, 4, 9, 16, 25)
# 或者用列表推导式转元组
squares = tuple([x ** 2 for x in source])
print(squares) # (1, 4, 9, 16, 25)
列表 vs 元组:如何选择?
| 特性 | 列表 | 元组 |
|---|---|---|
| 可变性 | 可变 | 不可变 |
| 语法 | [1, 2, 3] |
(1, 2, 3) |
| 性能 | 较慢 | 较快 |
| 内存 | 较大 | 较小 |
| 用作字典键 | 不行 | 可以 |
| 哈希 | 不可哈希 | 可哈希 |
| 适用场景 | 需要增删改的数据集 | 固定数据、函数返回值 |
# 选择参考示例
# 用列表:数据量需要经常变动
shopping_cart = [] # 购物车会频繁添加/删除商品
shopping_cart.append("牛奶")
shopping_cart.append("面包")
shopping_cart.remove("面包")
# 用元组:数据固定不变
DAYS_OF_WEEK = ("周一", "周二", "周三", "周四", "周五", "周六", "周日")
# 星期几是固定的,不应该被修改
# 用元组作为字典键(列表不行)
locations = {
(40.7128, -74.0060): "纽约",
(31.2304, 121.4737): "上海"
}
print(locations[(40.7128, -74.0060)]) # 纽约
性能对比
import time
# 列表和元组的创建速度
start = time.perf_counter()
for _ in range(1000000):
_ = (1, 2, 3)
tuple_time = time.perf_counter() - start
start = time.perf_counter()
for _ in range(1000000):
_ = [1, 2, 3]
list_time = time.perf_counter() - start
print(f"元组创建: {tuple_time:.4f}s")
print(f"列表创建: {list_time:.4f}s")
print(f"元组比列表快约 {list_time/tuple_time:.1f} 倍")
序列通用操作
列表、元组和字符串都是序列类型,它们共享许多通用操作:
# 序列通用操作演示
seq = [1, 2, 3, 4, 5]
# 成员检查
print(3 in seq) # True
print(10 not in seq) # True
# 连接和重复
print([1, 2] + [3, 4]) # [1, 2, 3, 4]
print("Hi " * 3) # Hi Hi Hi
print([0] * 5) # [0, 0, 0, 0, 0]
# len(), min(), max(), sum()
print(len([1, 2, 3])) # 3
print(min([3, 1, 4])) # 1
print(max([3, 1, 4])) # 4
print(sum([1, 2, 3])) # 6
# 遍历
for i, item in enumerate(["a", "b", "c"]):
print(f"{i}: {item}")
# 0: a
# 1: b
# 2: c
# zip 并行遍历
names = ["张三", "李四", "王五"]
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# 张三: 85
# 李四: 92
# 王五: 78
实践练习:学生成绩管理系统
# grade_manager.py
print("=" * 40)
print(" 学生成绩管理系统")
print("=" * 40)
# 学生成绩数据(列表存储)
students = [
("张三", 85),
("李四", 92),
("王五", 78),
("赵六", 95),
("钱七", 88),
]
# 显示所有学生
print("\n--- 学生列表 ---")
print(f"{'姓名':6s} {'成绩':6s}")
print("-" * 15)
for name, score in students:
print(f"{name:6s} {score:<6d}")
# 统计信息
scores = [score for _, score in students]
print(f"\n--- 统计信息 ---")
print(f"最高分: {max(scores)}")
print(f"最低分: {min(scores)}")
print(f"平均分: {sum(scores) / len(scores):.1f}")
# 按成绩排序
sorted_students = sorted(students, key=lambda x: x[1], reverse=True)
print(f"\n--- 成绩排名 ---")
print(f"{'名次':4s} {'姓名':6s} {'成绩':6s}")
print("-" * 20)
for i, (name, score) in enumerate(sorted_students, 1):
print(f"{i:<4d} {name:6s} {score:<6d}")
# 成绩分段统计
levels = {"优秀": 0, "良好": 0, "及格": 0, "不及格": 0}
for _, score in students:
if score >= 90:
levels["优秀"] += 1
elif score >= 80:
levels["良好"] += 1
elif score >= 60:
levels["及格"] += 1
else:
levels["不及格"] += 1
print(f"\n--- 成绩分段 ---")
for level, count in levels.items():
print(f"{level}: {count} 人")
常见陷阱
1. 可变默认参数
# 错误:在函数定义时就创建了可变对象
def add_item_bad(item, items=[]):
items.append(item)
return items
print(add_item_bad(1)) # [1]
print(add_item_bad(2)) # [1, 2](注意!上次的结果被保留了)
# 正确:使用 None 作为默认值
def add_item_good(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item_good(1)) # [1]
print(add_item_good(2)) # [2](每次都是新的列表)
2. 列表复制陷阱
# 错误
original = [1, 2, 3]
copy = original # 这只是引用,不是复制
copy.append(4)
print(original) # [1, 2, 3, 4](被改了!)
# 正确
original = [1, 2, 3]
copy = original.copy() # 或者 original[:]
copy.append(4)
print(original) # [1, 2, 3](没变)
3. 遍历时修改列表
# 错误:遍历时删除元素会出问题
items = [1, 2, 3, 4, 5]
for item in items:
if item % 2 == 0:
items.remove(item) # 可能跳过元素!
print(items) # [1, 3, 5](碰巧对了,但不安全)
# 正确:创建新列表
items = [1, 2, 3, 4, 5]
items = [item for item in items if item % 2 != 0]
print(items) # [1, 3, 5]
# 或者倒序遍历
items = [1, 2, 3, 4, 5]
for item in reversed(items):
if item % 2 == 0:
items.remove(item)
print(items) # [1, 3, 5]
4. 单元素元组
# 容易出错
not_tuple = (1) # 这是整数 1
print(type(not_tuple)) # <class 'int'>
# 正确写法
real_tuple = (1,) # 加逗号
print(type(real_tuple)) # <class 'tuple'>
# 空元组
empty = ()
print(type(empty)) # <class 'tuple'>
小结
在这篇文章中,我们学习了:
- 列表的创建、访问、修改和常用方法
- 列表推导式的强大用法
- 嵌套列表的操作
- 元组的创建、访问和不可变性
- 元组解包的多种用法
- 列表 vs 元组的选择标准和性能对比
- 序列通用操作和常用编程模式
下一步
掌握了列表和元组这两个序列类型后,下一篇文章我们将学习字典和集合——Python 中另外两种重要的数据结构。字典用于存储键值对映射,集合用于高效的元素去重和集合运算。
实践建议:创建一个包含学生信息的列表,练习增删改查操作。尝试写一个程序,用列表存储待办事项,支持添加、完成、删除和排序功能。
Summary: 列表与元组的创建操作方法详解。