前面我们学习了字符串,它是 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'>

小结

在这篇文章中,我们学习了:

  1. 列表的创建、访问、修改和常用方法
  2. 列表推导式的强大用法
  3. 嵌套列表的操作
  4. 元组的创建、访问和不可变性
  5. 元组解包的多种用法
  6. 列表 vs 元组的选择标准和性能对比
  7. 序列通用操作和常用编程模式

下一步

掌握了列表和元组这两个序列类型后,下一篇文章我们将学习字典和集合——Python 中另外两种重要的数据结构。字典用于存储键值对映射,集合用于高效的元素去重和集合运算。

实践建议:创建一个包含学生信息的列表,练习增删改查操作。尝试写一个程序,用列表存储待办事项,支持添加、完成、删除和排序功能。

Summary: 列表与元组的创建操作方法详解。