← 返回 Python 7天入门专题

Day 3 · 数据结构

列表 · 元组 · 字典 · 集合 · 嵌套结构 | 建议时长:3-4小时
第一性原理:单个变量只能存一个数据。但现实中数据往往是一堆——一个班50个学生的成绩、一个商品的所有属性。数据结构就是存放和组织大量数据的容器。不同的容器有不同的特长:列表擅长"排队",字典擅长"查名册",集合擅长"去重"。

0. 四大容器总览

容器比喻特征何时用
list 列表排队有序、可改、可重复存一组有顺序的数据
tuple 元组密封排队有序、不可改、可重复存一组不该改变的数据
dict 字典名册键值对、键唯一用"名字"快速查找"值"
set 集合去重篮无序、不可重复去重、判断包含关系
记忆口诀:列表能改能重复,元组只读不能改,字典键值对查找,集合去重不排序。

1. 列表 (list) — 最常用的容器

1.1 创建列表

# 创建列表用方括号
numbers = [1, 2, 3, 4, 5]
fruits = ["苹果", "香蕉", "橘子"]
mixed = [1, "hello", True, 3.14]  # 可以混合类型
empty = []                        # 空列表

# 用list()转换
chars = list("ABC")    # ['A', 'B', 'C']

1.2 索引与切片

fruits = ["苹果", "香蕉", "橘子", "葡萄", "西瓜"]

# 索引(从0开始,-1是最后一个)
print(fruits[0])     # 苹果
print(fruits[-1])    # 西瓜
print(fruits[2])     # 橘子

# 切片 [起:止:步] —— 左闭右开
print(fruits[1:3])    # ['香蕉', '橘子'] (索引1到2)
print(fruits[:3])    # ['苹果', '香蕉', '橘子'] (从头到2)
print(fruits[2:])    # ['橘子', '葡萄', '西瓜'] (从2到尾)
print(fruits[::2])   # ['苹果', '橘子', '西瓜'] (步长2)
print(fruits[::-1])  # ['西瓜', '葡萄', '橘子', '香蕉', '苹果'] (反转)

1.3 增删改

lst = [1, 2, 3]

# 改
lst[0] = 10          # [10, 2, 3]

# 增
lst.append(4)        # [10, 2, 3, 4] 末尾添加
lst.insert(1, 99)    # [10, 99, 2, 3, 4] 在索引1处插入
lst.extend([5, 6])   # [10, 99, 2, 3, 4, 5, 6] 合并另一个列表

# 删
lst.remove(99)       # 删除第一个值为99的元素
lst.pop()            # 删除并返回最后一个元素
lst.pop(0)           # 删除并返回索引0的元素
del lst[0]         # 删除索引0的元素
lst.clear()          # 清空列表 → []

1.4 常用方法

nums = [3, 1, 4, 1, 5, 9, 2, 6]

# 查找
print(len(nums))         # 8 (长度)
print(nums.count(1))       # 2 (值1出现2次)
print(nums.index(5))       # 4 (值5的索引)
print(5 in nums)          # True (是否包含)

# 排序
nums.sort()               # 原地排序 [1, 1, 2, 3, 4, 5, 6, 9]
nums.sort(reverse=True)  # 降序 [9, 6, 5, 4, 3, 2, 1, 1]
nums.reverse()            # 反转

# sorted() — 返回新列表,不改原列表
original = [3, 1, 2]
new_list = sorted(original)  # [1, 2, 3],original不变

# 复制
copy1 = nums.copy()       # 方法1
copy2 = nums[:]           # 方法2(切片)
copy3 = list(nums)        # 方法3

1.5 列表推导式(Python特色,必须掌握)

# 传统写法
squares = []
for i in range(10):
    squares.append(i ** 2)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 推导式写法(一行搞定)
squares = [i ** 2 for i in range(10)]

# 带条件
evens = [i for i in range(20) if i % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

# 带条件表达式
labels = ["偶" if i % 2 == 0 else "奇" for i in range(5)]
# ['偶', '奇', '偶', '奇', '偶']

# 嵌套推导式
matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]
# [[1, 2, 3], [2, 4, 6], [3, 6, 9]]
列表推导式语法:[表达式 for 变量 in 可迭代对象 if 条件]
理解顺序:先for循环遍历,再if过滤,最后用表达式生成元素。

2. 元组 (tuple) — 不可改的列表

第一性原理:有些数据创建后不该被修改(如一年12个月、一周7天)。元组就是"只读列表"——创建后不能增删改,更安全、更快。
# 创建元组用圆括号
months = ("一月", "二月", "三月")
point = (3, 4)           # 坐标
single = (42,)           # ⚠️ 单元素元组必须加逗号!
empty = ()               # 空元组

# 不用括号也行(自动打包)
t = 1, 2, 3              # (1, 2, 3)
a, b, c = t              # 解包:a=1, b=2, c=3

# 元组支持索引和切片(和列表一样)
print(months[0])        # 一月
print(months[1:])       # ('二月', '三月')

# ⚠️ 元组不能修改!
# months[0] = "1月"  ← 报错!TypeError

# 元组的方法只有 count() 和 index()
print(point.count(3))    # 1
print(point.index(4))    # 1

2.1 元组解包(超实用)

# 基本解包
x, y = 10, 20
x, y = y, x              # 交换变量

# 用 * 收集剩余
first, *rest = [1, 2, 3, 4, 5]
print(first)  # 1
print(rest)   # [2, 3, 4, 5]

*front, last = [1, 2, 3, 4, 5]
print(front)  # [1, 2, 3, 4]
print(last)   # 5

# 函数返回多个值(本质是元组)
def min_max(lst):
    return min(lst), max(lst)

low, high = min_max([3, 1, 4, 1, 5])  # low=1, high=5

3. 字典 (dict) — 键值对容器

第一性原理:列表用"位置"找数据(第0个、第1个),但有时候你想用"名字"找数据——用学号找学生、用商品名找价格。字典就是用键(key)查找值(value)的容器,查找速度极快。

3.1 创建字典

# 用花括号创建
student = {
    "name": "张三",
    "age": 20,
    "grade": "大三",
    "courses": ["数学", "英语", "Python"]
}

# 用dict()创建
prices = dict(apple=5, banana=3, orange=4)

# 空字典
empty = {}

3.2 访问与修改

student = {"name": "张三", "age": 20}

# 访问
print(student["name"])       # 张三
print(student.get("age"))    # 20
print(student.get("phone"))  # None (键不存在返回None)
print(student.get("phone", "未填写"))  # 未填写 (指定默认值)

# ⚠️ 用[]访问不存在的键会报错!
# student["phone"]  ← KeyError!

# 修改/添加
student["age"] = 21            # 修改已有键
student["phone"] = "13800000000"  # 添加新键

# 删除
del student["phone"]        # 删除键值对
student.pop("age")         # 删除并返回值
student.popitem()          # 删除最后一个键值对
student.clear()            # 清空

3.3 遍历字典

student = {"name": "张三", "age": 20, "grade": "大三"}

# 遍历键
for key in student:
    print(key)

# 遍历键值对(最常用)
for key, value in student.items():
    print(f"{key}: {value}")
# name: 张三
# age: 20
# grade: 大三

# 只遍历值
for v in student.values():
    print(v)

# 只遍历键
for k in student.keys():
    print(k)

3.4 字典常用方法

d = {"a": 1, "b": 2}

# 检查键是否存在
print("a" in d)         # True
print("c" not in d)     # True

# 合并字典
d.update({"c": 3, "d": 4})   # d = {'a':1,'b':2,'c':3,'d':4}

# setdefault — 键不存在时设置默认值
d.setdefault("e", 5)       # 添加 'e':5
d.setdefault("a", 99)      # 'a'已存在,不做任何事

# 字典推导式
squares = {i: i**2 for i in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

4. 集合 (set) — 去重利器

第一性原理:集合的两大核心能力:去重(自动去掉重复元素)和集合运算(交集、并集、差集)。
# 创建集合用花括号
s1 = {1, 2, 3, 4, 5}
s2 = {3, 4, 5, 6, 7}

# ⚠️ 空集合不能用{}(那是空字典),要用set()
empty_set = set()

# 从列表去重
nums = [1, 2, 2, 3, 3, 3, 4]
unique = set(nums)    # {1, 2, 3, 4}
unique_list = list(set(nums))  # [1, 2, 3, 4] 转回列表

# 集合运算
print(s1 & s2)    # 交集 {3, 4, 5}  等价 s1.intersection(s2)
print(s1 | s2)    # 并集 {1, 2, 3, 4, 5, 6, 7}  等价 s1.union(s2)
print(s1 - s2)    # 差集 {1, 2}  等价 s1.difference(s2)
print(s1 ^ s2)    # 对称差集 {1, 2, 6, 7}  等价 s1.symmetric_difference(s2)

# 子集/超集判断
print({1, 2} <= {1, 2, 3})   # True (是子集)
print({1, 2, 3} >= {1, 2})   # True (是超集)

# 增删
s1.add(6)          # 添加
s1.remove(1)       # 删除(不存在会报错)
s1.discard(99)     # 删除(不存在不报错)

5. 嵌套结构 — 真实场景的数据组织

5.1 列表嵌套(二维列表)

# 矩阵
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# 访问
print(matrix[0])       # [1, 2, 3]
print(matrix[1][2])    # 6 (第2行第3列)

# 遍历
for row in matrix:
    for val in row:
        print(val, end=" ")
    print()

5.2 字典列表(最常见的数据结构)

# 学生列表
students = [
    {"name": "张三", "age": 20, "score": 85},
    {"name": "李四", "age": 22, "score": 92},
    {"name": "王五", "age": 21, "score": 78},
]

# 遍历
for student in students:
    print(f"{student['name']}: {student['score']}分")

# 查找
for s in students:
    if s["name"] == "李四":
        print(s)
        break

# 筛选:成绩>80的学生
good = [s for s in students if s["score"] > 80]

# 排序:按成绩降序
students.sort(key=lambda s: s["score"], reverse=True)

5.3 字典嵌套字典

# 商品目录
products = {
    "iphone": {"price": 7999, "stock": 50, "brand": "Apple"},
    "galaxy": {"price": 5999, "stock": 30, "brand": "Samsung"},
    "mi14":   {"price": 3999, "stock": 100, "brand": "Xiaomi"},
}

# 访问
print(products["iphone"]["price"])  # 7999

# 遍历
for name, info in products.items():
    print(f"{info['brand']} {name}: ¥{info['price']} (库存{info['stock']})")

6. 可变 vs 不可变(重要概念)

可变类型

创建后可以修改内容

  • list — [1,2,3]
  • dict — {'a':1}
  • set — {1,2,3}

修改时内存地址不变

不可变类型

创建后不能修改内容

  • int, float, bool
  • str — "hello"
  • tuple — (1,2,3)

"修改"实际上是创建新对象

⚠️ 浅拷贝陷阱:直接赋值只是引用同一个对象,修改一个另一个也变。
a = [1, 2, 3]
b = a          # b和a指向同一个列表!
b.append(4)
print(a)      # [1, 2, 3, 4] ← a也变了!

# 正确做法
b = a.copy()   # 浅拷贝,独立副本
b = a[:]       # 同上
import copy
b = copy.deepcopy(a)  # 深拷贝(嵌套结构用这个)

7. 练习题(独立完成)

练习1:成绩统计

给定成绩列表 scores = [85, 92, 78, 65, 95, 88, 72, 90, 85, 60],计算:最高分、最低分、平均分、及格人数(≥60)、优秀人数(≥90)。

参考答案
scores = [85, 92, 78, 65, 95, 88, 72, 90, 85, 60]

print(f"最高分: {max(scores)}")
print(f"最低分: {min(scores)}")
print(f"平均分: {sum(scores) / len(scores):.1f}")
print(f"及格人数: {sum(1 for s in scores if s >= 60)}")
print(f"优秀人数: {sum(1 for s in scores if s >= 90)}")

练习2:单词频率统计

输入一段英文,统计每个单词出现的次数,按频率从高到低输出。

参考答案
text = "the cat sat on the mat the cat ate the rat"
words = text.split()

freq = {}
for word in words:
    freq[word] = freq.get(word, 0) + 1

for word, count in sorted(freq.items(), key=lambda x: x[1], reverse=True):
    print(f"{word}: {count}")
# the: 4, cat: 2, sat: 1, on: 1, mat: 1, ate: 1, rat: 1

练习3:通讯录管理

实现一个通讯录:支持添加联系人(姓名→电话)、查找联系人、删除联系人、显示所有联系人。用字典存储,用while循环做菜单。

参考答案
contacts = {}

while True:
    print("\n1.添加 2.查找 3.删除 4.显示 5.退出")
    choice = input("选择: ")
    
    if choice == "1":
        name = input("姓名: ")
        phone = input("电话: ")
        contacts[name] = phone
        print("添加成功")
    elif choice == "2":
        name = input("姓名: ")
        phone = contacts.get(name, "未找到")
        print(f"{name}: {phone}")
    elif choice == "3":
        name = input("姓名: ")
        if name in contacts:
            del contacts[name]
            print("删除成功")
        else:
            print("未找到")
    elif choice == "4":
        for name, phone in contacts.items():
            print(f"{name}: {phone}")
    elif choice == "5":
        break

练习4:列表去重保序

给定列表 [3,1,4,1,5,9,2,6,3,5,3],去重但保持原始顺序(不能用set直接转,因为set无序)。

参考答案
nums = [3, 1, 4, 1, 5, 9, 2, 6, 3, 5, 3]

seen = set()
result = []
for n in nums:
    if n not in seen:
        result.append(n)
        seen.add(n)
print(result)  # [3, 1, 4, 5, 9, 2, 6]

练习5:学生成绩管理

用字典列表存储5个学生的姓名、年龄、三门科目成绩。计算每个学生的平均分,找出平均分最高的学生。

参考答案
students = [
    {"name": "张三", "age": 20, "scores": [85, 90, 78]},
    {"name": "李四", "age": 21, "scores": [92, 88, 95]},
    {"name": "王五", "age": 19, "scores": [70, 65, 80]},
    {"name": "赵六", "age": 22, "scores": [88, 92, 90]},
    {"name": "钱七", "age": 20, "scores": [75, 82, 79]},
]

for s in students:
    avg = sum(s["scores"]) / len(s["scores"])
    s["avg"] = avg
    print(f"{s['name']}: 平均{avg:.1f}")

best = max(students, key=lambda s: s["avg"])
print(f"\n最高分: {best['name']} ({best['avg']:.1f})")

8. Day 3 小结

今天你学会了什么