第3章 3.1 索引数组与关联数组

学习目标:学完本文,你能用 Python 的「列表」和「字典」管理一堆数据,再也不用手忙脚乱地数第几个元素了。


🎯 开场 3 分钟:为什么要学这个?

你有没有遇到过这种情况——

双十一老婆让你帮她记购物车里的宝贝:「那个粉色的、对就是那个、贵的那个……」

你内心 OS:「到底是哪个???」

这就是没有给数据贴标签的痛苦。想象一下,如果购物车里的每件商品都有一张「小票」,写着「商品名叫啥、价格多少、还剩几件」,那该多清楚!

编程里,索引数组(列表)就是那张「按顺序编号的小票」,关联数组(字典)就是那张「写满标签的详细小票」。

今天这套组合拳学会了,以后处理 Excel、JSON 接口返回、爬虫数据,全都不在话下。


🧱 基础 25 分钟:核心概念

3.1.1 索引数组(列表)= 食堂打饭的餐盘

生活类比:食堂打饭的时候,每个菜放在一个格子里,从左到右排成一排。第一格是米饭,第二格是红烧肉,第三格是青菜……你知道「第二格\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\n\n」就是红烧肉,不需要记它叫啥。

Python 里的列表就是这个原理——一堆元素排排坐,每个位置有个编号(从 0 开始)。

为什么要用:当你有一堆「同类东西」需要按顺序放着、挨个处理的时候。比如:10 个用户的名字、一个月每天的温度、一购物车的商品。

怎么用

# 创建一个购物清单(索引数组)
fruits = ["苹果", "香蕉", "橙子", "草莓"]

# 按位置取第三个水果(注意!位置是从 0 开始数的)
print(fruits[2])  # 输出:橙子

这行代码在干嘛:取出 fruits 里第 3 个位置的东西。类比食堂打饭——走到第 3 格拿橙子。


3.1.2 关联数组(字典)= 带名牌的快递柜

生活类比:小区快递柜大家都见过吧?每个格子里放的是「1-2-3 号张阿姨的快递」「2-1-5 号李叔叔的快递」——每个格子有个「编号 + 收件人」的双重标签。

Python 里的字典就是这个原理——每个元素有一个「名字」(叫 key),对应一个「值」(叫 value)。

为什么要用:当你需要「根据名字找东西」的时候。比如:根据用户名查用户信息、根据商品 ID 查价格、根据城市名查天气。

怎么用

# 创建一个用户信息(关联数组)
user = {
"name": "张三",
"age": 28,
"city": "北京"
}

# 根据名字取年龄
print(user["age"])  # 输出:28

这行代码在干嘛:从 user 字典里找到「age」这个标签对应的值。类比快递柜——找到「李叔叔」那个格子,看里面是啥。


3.1.3 混合使用:购物车里的战斗机

真实场景往往是列表里套字典,就像一个购物车里放着好几件商品,每件商品有自己的详细信息:

# 购物车:3 件商品,每件商品是一个字典
cart = [
{"name": "iPhone 15", "price": 6999, "count": 1},
{"name": "AirPods Pro", "price": 1899, "count": 2},
{"name": "MagSafe", "price": 299, "count": 1}
]

# 算算总价
total = 0
for item in cart:
total += item["price"] * item["count"]

print(f"总价:{total} 元")  # 输出:总价:10796 元

这段代码在干嘛:遍历购物车每件商品,把价格乘数量加起来。类比——收银员扫每件商品的条码,结账总额。


3.1.4 常用操作 5 分钟入门

下面这些操作,抄过去就能跑,遇到类似场景直接复制改名字就行:

# 1. 创建空列表 / 空字典
empty_list = []
empty_dict = {}

# 2. 添加元素
fruits = ["苹果", "香蕉"]
fruits.append("橙子")          # 列表末尾加一个
print(fruits)                  # 输出:['苹果', '香蕉', '橙子']

# 3. 修改元素
fruits[0] = "大苹果"           # 把第 1 个改成大苹果
print(fruits)                  # 输出:['大苹果', '香蕉', '橙子']

# 4. 删除元素
del fruits[1]                  # 删掉第 2 个
print(fruits)                  # 输出:['大苹果', '橙子']

# 5. 字典增改
user = {"name": "张三"}
user["age"] = 28               # 新增一个键值对
user["name"] = "李四"          # 修改已有的键
print(user)                    # 输出:{'name': '李四', 'age': 28}

# 6. 获取长度
print(len(fruits))             # 输出:2(几个元素)
print(len(user))               # 输出:2(几个键值对)

# 7. 检查键是否存在
if "age" in user:
print(f"年龄是 {user['age']}")  # 输出:年龄是 28

记住口诀:列表用位置,字典用名字,混合用循环


3.1.5 列表推导式:一行代码搞定「筛选 + 转换」

这是 Python 的独门秘籍,其他语言要写好几行,Python 一行搞定。

生活类比:你去自助餐厅,想把所有甜品挑出来——列表推导式就是那个帮你快速挑菜的小托盘。

# 场景:把购物车里价格超过 1000 块的商品挑出来
cart = [
{"name": "iPhone 15", "price": 6999},
{"name": "数据线", "price": 29},
{"name": "AirPods Pro", "price": 1899}
]

# 传统写法(3 行)
expensive = []
for item in cart:
if item["price"] > 1000:
    expensive.append(item)

# 列表推导式(1 行搞定)
expensive = [item for item in cart if item["price"] > 1000]

print(expensive)
# 输出:[{'name': 'iPhone 15', 'price': 6999}, {'name': 'AirPods Pro', 'price': 1899}]

读法:从 cart 里,把每个 item(如果你喜欢可以改名),但只保留 price > 1000 的。翻译成人话——「把所有贵的挑出来」。


🔥 实战 35 分钟:3 个递进的小项目

项目 1(5 分钟):班级成绩统计器

需求:输入 5 个人的成绩,统计平均分和最高分。

# -*- coding: utf-8 -*-
# 班级成绩统计器

scores = [85, 92, 78, 96, 88]

average = sum(scores) / len(scores)
max_score = max(scores)
min_score = min(scores)

print(f"班级平均分:{average:.1f}")
print(f"最高分:{max_score},最低分:{min_score}")

# 顺便找出最高分的是谁(这里用位置模拟姓名)
top_index = scores.index(max_score)
names = ["小明", "小红", "小刚", "小芳", "小李"]
print(f"最高分选手:{names[top_index]}")

预期输出

班级平均分:87.8
最高分:96,最低分:78
最高分选手:小芳

一句话解释:用 sum/len/max 三个函数搞定统计,index() 找到最高分的位置,再用位置找对应姓名。


项目 2(15 分钟):CSV 文件读取 + 数据清洗

需求:读取一份「用户消费记录.csv」,找出消费总额前 3 的用户,打印排行榜。

先模拟一个 CSV 文件内容(实际运行时存成 data.csv):

用户名,城市,消费金额
张三,北京,3500
李四,上海,8200
王五,广州,1500
赵六,深圳,6700
小明,北京,4200
小红,上海,9800

完整可运行代码

# -*- coding: utf-8 -*-
# 用户消费排行榜

import csv

# 模拟 CSV 数据(实际用 open('data.csv', encoding='utf-8') 读取)
csv_data = """用户名,城市,消费金额
张三,北京,3500
李四,上海,8200
王五,广州,1500
赵六,深圳,6700
小明,北京,4200
小红,上海,9800"""

# 解析 CSV
lines = csv_data.strip().split("\n")
reader = csv.DictReader(lines)

# 转换成字典列表
users = []
for row in reader:
users.append({
    "name": row["用户名"],
    "city": row["城市"],
    "spending": int(row["消费金额"])
})

# 按消费金额排序(降序)
sorted_users = sorted(users, key=lambda x: x["spending"], reverse=True)

# 打印前 3 名
print("🏆 消费排行榜 TOP 3")
print("-" * 25)
for i, user in enumerate(sorted_users[:3], 1):
medal = ["🥇", "🥈", "🥉"][i-1]
print(f"{medal} 第{i}名:{user['name']}({user['city']})- 消费 {user['spending']} 元")

预期输出

🏆 消费排行榜 TOP 3
-------------------------
🥇 第1名:小红(上海)- 消费 9800 元
🥈 第2名:李四(上海)- 消费 8200 元
🥉 第3名:赵六(深圳)- 消费 6700 元

一句话解释csv.DictReader 自动把每行转成字典,sortedlambda 按消费金额倒序排列。


项目 3(15 分钟):待办事项管理器(命令行版)

需求:做一个命令行待办清单,能增、查、显、删。用字典存每个待办事项的详情。

# -*- coding: utf-8 -*-
# 命令行待办事项管理器

todos = []

def add_todo():
task = input("请输入新任务:")
priority = input("优先级(高/中/低):")
todos.append({
    "task": task,
    "priority": priority,
    "done": False
})
print(f"✅ 已添加:「{task}」")

def list_todos():
if not todos:
    print("📭 没有任何待办事项")
    return
print("\n📋 待办清单")
print("-" * 40)
for i, item in enumerate(todos, 1):
    status = "✅" if item["done"] else "⬜"
    tag = {"高": "🔴", "中": "🟡", "低": "🟢"}.get(item["priority"], "⚪")
    done_mark = "(已完成)" if item["done"] else ""
    print(f"{i}. {status} {tag}{item['task']} {done_mark}")

def done_todo():
list_todos()
if not todos:
    return
idx = int(input("输入要完成的任务编号:")) - 1
if 0 <= idx < len(todos):
    todos[idx]["done"] = True
    print(f"🎉 「{todos[idx]['task']}」已完成!")
else:
    print("❌ 编号无效")

def delete_todo():
list_todos()
if not todos:
    return
idx = int(input("输入要删除的任务编号:")) - 1
if 0 <= idx < len(todos):
    removed = todos.pop(idx)
    print(f"🗑️ 已删除:「{removed['task']}」")
else:
    print("❌ 编号无效")

# 主循环
while True:
print("\n📌 待办事项管理器")
print("1. 添加任务  2. 查看全部  3. 标记完成  4. 删除任务  5. 退出")
choice = input("请选择:")
if choice == "1":
    add_todo()
elif choice == "2":
    list_todos()
elif choice == "3":
    done_todo()
elif choice == "4":
    delete_todo()
elif choice == "5":
    print("下次见!👋")
    break
else:
    print("⚠️ 无效选择,请重新输入")

预期输出(示例交互):

📌 待办事项管理器
1. 添加任务  2. 查看全部  3. 标记完成  4. 删除任务  5. 退出
请选择:1
请输入新任务:写周报
优先级(高/中/低):高
✅ 已添加:「写周报」

📌 待办事项管理器
1. 添加任务  2. 查看全部  3. 标记完成  4. 删除任务  5. 退出
请选择:2

📋 待办清单
----------------------------------------
1. ⬜ 🔴写周报

一句话解释:用列表存所有待办,每个待办是个字典包含任务名、优先级、是否完成,增删改查全靠列表的 append/pop 和字典的键值访问。


💪 进阶 20 分钟:常见坑 + 性能小贴士

坑 1:列表切片越界不会报错?

# ❌ 错误示范
fruits = ["苹果", "香蕉"]
print(fruits[5])  # 越界了,Python 会直接报错 IndexError

# ✅ 正确示范
fruits = ["苹果", "香蕉"]
if len(fruits) > 5:
print(fruits[5])
else:
print("只有 2 个水果,没有第 6 个")

说白了:列表是个老实人,你问它要第 6 个它没有,它会直接喊「没有!」报错。取之前先数数有几个。


坑 2:字典访问键不存在会炸?

# ❌ 错误示范
user = {"name": "张三"}
print(user["age"])  # 报错 KeyError: 'age'

# ✅ 正确示范 1:用 get 方法(推荐)
print(user.get("age", "未知"))  # 输出:未知(安全取,不报错)

# ✅ 正确示范 2:先检查有没有
if "age" in user:
print(user["age"])

说白了get 像是带保险的访问——你要的东西没有,它返回一个默认值而不是直接崩溃。


坑 3:列表里套字典,修改时搞错对象?

# ❌ 错误示范:以为修改的是复制,其实改的是原数据
users = [{"name": "张三"}, {"name": "李四"}]
target = users[0]
target["name"] = "王五"
print(users[0]["name"])  # 输出:王五(原来的也被改了!)

# ✅ 正确示范:用 deepcopy 如果真的需要独立副本
from copy import deepcopy
users = [{"name": "张三"}, {"name": "李四"}]
target = deepcopy(users[0])
target["name"] = "王五"
print(users[0]["name"])  # 输出:张三(原来的没变)

说白了:列表里存的是「房卡」不是「房子」,你拿到的只是引用。要真正复制一份房子,得用 deepcopy


坑 4:循环里修改列表导致索引错位?

# ❌ 错误示范:边遍历边删,会漏删或报错
nums = [1, 2, 3, 4, 5]
for i, n in enumerate(nums):
if n % 2 == 0:
    del nums[i]
print(nums)  # 结果可能不是 [1, 3, 5]!

# ✅ 正确示范:过滤出新列表
nums = [1, 2, 3, 4, 5]
nums = [n for n in nums if n % 2 != 0]  # 列表推导式过滤
print(nums)  # 输出:[1, 3, 5]

说白了:不要在数数的过程中删人,数着数着就乱了。


坑 5:字典的键大小写敏感?

# ❌ 错误示范:以为 Key 和 key 是同一个
config = {"Key": "value1"}
print(config["key"])  # 报错 KeyError

# ✅ 正确示范:统一大小写
config = {"key": "value1"}
print(config["key"])  # 输出:value1

性能小贴士:列表推导式比普通循环快

import time

# 性能测试
n = 1000000

# 方式 1:普通循环
start = time.time()
result1 = []
for i in range(n):
result1.append(i * 2)
print(f"普通循环:{time.time() - start:.3f}秒")

# 方式 2:列表推导式
start = time.time()
result2 = [i * 2 for i in range(n)]
print(f"列表推导式:{time.time() - start:.3f}秒")

列表推导式通常快 30%-50%,因为 Python 底层对它有优化。


调试技巧:print 大法已经够用了

# 场景:不知道循环里发生了什么
cart = [
{"name": "iPhone", "price": 6999},
{"name": "数据线", "price": 29}
]

for item in cart:
print(f"DEBUG: 正在处理 {item['name']}, 价格={item['price']}")
# 看到输出没问题后,删掉这行或注释掉

说白了:别小看 print,老手调试 80% 的问题靠的就是它。剩下 20% 才需要 pdblogging


✏️ 练习题

练习 1(2 分钟):改改数据

  • 输入[10, 20, 30, 40, 50]
  • 预期输出第三个元素是 30
  • 提示list[index] 的 index 从 0 开始

练习 2(2 分钟):加个判断

  • 输入:列表 [85, 92, 78]
  • 预期输出:如果最高分大于 90,输出「优秀」,否则输出「还需努力」
  • 提示max() 求最大值,配合 if 判断

练习 3(3 分钟):处理新数据

  • 输入:字典 {"北京": 22, "上海": 25, "广州": 28, "深圳": 30}
  • 预期输出:打印每个城市和温度,格式:城市:温度℃
  • 提示:用 for city, temp in dict.items() 遍历

练习 4(5 分钟):串联两个项目

  • 输入:用户列表 [{"name": "A", "score": 85}, {"name": "B", "score": 92}]
  • 预期输出:找出最高分的人,打印 最高分是:B,92分
  • 提示:参考项目 1 的 max + 项目 2 的字典取值

练习 5(5 分钟):这个报错啥意思?

# 代码
d = {"name": "张三"}
print(d["age"])

# 报错信息
# KeyError: 'age'
  • 问题:为什么会报错?怎么修?
  • 提示:字典里没有 age 这个键

作业:做一个「个人记账本」

需求:做一个命令行记账工具,能记录收入和支出,查询指定月份的统计。

功能点
1. 添加账目(收入/支出、金额、备注、月份)
2. 查看所有账目
3. 按月份统计(该月总收入、总支出、结余)
4. 删除指定账目

加分项
1. 数据持久化(存到文件里,重启不丢)
2. 支持按类型筛选(只看支出或只看收入)

验收标准
- 能跑起来
- 每个功能有明确输出
- 代码有适当注释

提交方式:评论区贴代码或 GitHub 链接


📚 总结 + 资源

本文学了 3 件事
1. 列表(索引数组)= 按位置编号的容器,用 [] 操作
2. 字典(关联数组)= 带名字标签的容器,用 [].get() 操作
3. 列表里套字典 = 处理真实业务数据的黄金组合

延伸学习资源
- 官方文档:Python 列表 | Python 字典
- 书籍:《Python 编程:从入门到实践》第 5 章「字典」

互动钩子:你在实际工作或生活中,用列表和字典解决过什么问题?评论区聊聊,老粉优先回复!👇


📌 下章预告:学会了列表和字典的基本操作,下一章我们要学习 20 个高阶数组函数,让数据处理快到飞起~

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。