增删改查、统计平均分、排名、及格率、导出文件——数据管理程序的标准套路
一、场景:期末了,成绩单一团乱
假设你是老师(或者家长,要管理孩子的各科成绩):
- 语文85,数学92,英语78——记在纸上
- 想算全班平均分?拿计算器一个个加
- 想知道谁进步了?翻上次的记录
- 领导要一份”及格率统计表”?重新算一遍再誊抄
一件纯机械的事,耗掉一晚上。
今天我们做一个成绩管理器:录入成绩、查看名单、算平均分、找最高最低、排名、算及格率、导出成文件。
你可能觉得这是”数据库程序”,很高大上。其实就是前面所有知识的组合拳:
- 字典存一个学生的成绩(姓名→分数的对应)
- 列表存全班学生(一堆字典排好队)
- 循环+条件做菜单和增删改查
- 文件操作让数据关了程序也不丢
学会它,你就会发现:通讯录、库存管理、记账本,全是同一个套路,换个字段名而已。
二、先搞懂数据结构
一个学生的成绩,用字典表示:
{"姓名": "小明", "语文": 85, "数学": 92, "英语": 78}
全班学生,用列表装起来:
students = [
{"姓名": "小明", "语文": 85, "数学": 92, "英语": 78},
{"姓名": "小红", "语文": 90, "数学": 88, "英语": 95},
{"姓名": "小刚", "语文": 60, "数学": 55, "英语": 70},
]
“列表套字典”——这是Python里最常用的数据组织方式,没有之一。字典描述”一个东西的各项属性”,列表把”一堆东西”排好队。以后你处理任何”一行一行的表格数据”,都是这个结构。
怎么存到文件里?
第12篇学过文件读写,但列表和字典不能直接f.write()。这里要用一个新朋友:json模块。
json是Python自带的工具箱,专门把列表/字典变成字符串(存进文件),以及把字符串变回列表/字典(读出来)。就像把乐高城堡拆成扁平的积木块装箱(存),要用时再拼回来(读)。
用法就两个函数:
import json
# 存:把列表变成JSON字符串写进文件
json.dump(students, f, ensure_ascii=False, indent=2)
# 读:把文件里的JSON字符串变回列表
students = json.load(f)
ensure_ascii=False:不加它,中文会变成u5c0fu660e这种乱码indent=2:让文件内容带缩进,好看(方便你打开检查)
三、先跟AI聊
你的提示词:
用Python写一个成绩管理器。用列表存学生数据,每个学生是字典(姓名、语文、数学、英语)。有菜单循环:1录入成绩 2查看全部 3统计信息 4退出。数据用json保存到文件,程序启动时自动加载。加上详细的中文注释。
AI会给你一段代码,大概长这样:
import json
FILENAME = "scores.json"
def load_scores():
"""从文件加载成绩数据"""
try:
with open(FILENAME, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return [] # 文件不存在就返回空列表
def save_scores(students):
"""把成绩数据保存到文件"""
with open(FILENAME, "w", encoding="utf-8") as f:
json.dump(students, f, ensure_ascii=False, indent=2)
def show_all(students):
"""显示所有学生的成绩"""
if len(students) == 0:
print("还没有成绩记录")
return
print(f"n{'姓名':6}{'数学':>6}{'英语':>6}{'总分':>8}{'平均':>8}")
print("-" * 48)
for s in students:
total = s["语文"] + s["数学"] + s["英语"]
avg = total / 3
print(f"{s['姓名']:6}{s['数学']:>6}{s['英语']:>6}{total:>8}{avg:>8.1f}")
# === 主程序 ===
students = load_scores() # 启动时加载已有数据
print("=== 成绩管理器 ===")
while True:
print("n1. 录入成绩 2. 查看全部 3. 退出")
choice = input("请选择:")
if choice == "1":
name = input("姓名:")
chinese = int(input("语文成绩:"))
math = int(input("数学成绩:"))
english = int(input("英语成绩:"))
# 组装成字典,加进列表
students.append({
"姓名": name,
"语文": chinese,
"数学": math,
"英语": english,
})
save_scores(students) # 立即保存
print(f"已录入 {name} 的成绩")
elif choice == "2":
show_all(students)
elif choice == "3":
print("再见!")
break
else:
print("无效选择,请输入1-3")
四、逐行理解
FILENAME = "scores.json" —— 大写常量
文件名只在一个地方定义,后面全用变量。哪天想改文件名,只改一行。用大写是程序员的习惯,表示”这个值不会变”。
load_scores():第一次见try/except这样用
try:
with open(FILENAME, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
第一次运行程序时,文件还不存在,直接打开会崩。所以用try...except兜底:文件不存在不是错误,只是”还没有数据”,返回空列表就好。
这个思路很常见:把”预期内的意外”用except转成正常逻辑。
save_scores():两行搞定持久化
json.dump(students, f, ensure_ascii=False, indent=2)
json.dump(数据, 文件):把数据变成JSON格式写进文件。存完后scores.json长这样:
[
{
"姓名": "小明",
"语文": 85,
"数学": 92,
"英语": 78
},
{
"姓名": "小红",
"语文": 90,
"数学": 88,
"英语": 95
}
]
每次录入后立即save_scores(),这样程序意外退出数据也不丢。这叫”写时保存”。
show_all():表格打印
print(f"{s['姓名']:6}{s['数学']:>6}{s['英语']:>6}{total:>8}{avg:>8.1f}")
第15篇学过的对齐魔法又来了:<10左对齐占10位,>6右对齐占6位。{avg:>8.1f}里的.1f表示保留1位小数(f是float格式化)。
s['姓名']——注意字典的key是中文字符串,用引号包起来。
菜单循环
while True:
choice = input("请选择:")
if choice == "1":
...
elif choice == "3":
break
标准的菜单套路:循环显示选项 → 输入数字 → 对应分支干活 → 循环回来。程序不退出,数据一直”热”在内存里。
五、运行试试
=== 成绩管理器 ===
1. 录入成绩 2. 查看全部 3. 退出
请选择:1
姓名:小明
语文成绩:85
数学成绩:92
英语成绩:78
已录入 小明 的成绩
1. 录入成绩 2. 查看全部 3. 退出
请选择:2
姓名 语文 数学 英语 总分 平均
------------------------------------------------
小明 85 92 78 255 85.0
录入三个人后退出程序,再重新运行,选2查看——数据还在!因为是从scores.json加载的。这就是”持久化”:数据的生命比程序长。
六、第一个改进:统计信息(平均/最高/最低)
加一个菜单选项3″统计信息”,原来的退出改成4:
def stats(students):
"""统计全班平均分、最高分、最低分"""
if len(students) == 0:
print("还没有成绩记录")
return
# 先算出每个人的总分
totals = []
for s in students:
totals.append(s["语文"] + s["数学"] + s["英语"])
# 全班平均分 = 所有总分之和 ÷ 人数 ÷ 3科
avg_all = sum(totals) / len(totals) / 3
print(f"n全班平均分:{avg_all:.1f}")
# 找最高分和最低分(记录下标)
best_i = 0
worst_i = 0
for i in range(len(totals)):
if totals[i] > totals[best_i]:
best_i = i
if totals[i] < totals[worst_i]:
worst_i = i
print(f"最高分:{students[best_i]['姓名']}(总分{totals[best_i]})")
print(f"最低分:{students[worst_i]['姓名']}(总分{totals[worst_i]})")
几个值得学的套路
totals列表:先用循环把每个人的总分算出来单独存一个列表。因为总分要用三次(平均、找最高、找最低),提前算好不重复劳动。
sum():求和内置函数,sum([255, 273, 185]) = 713。不用自己写循环累加。
找最大最小的”记录下标”法:
best_i = 0
for i in range(len(totals)):
if totals[i] > totals[best_i]:
best_i = i
假设第0个是最高,然后挨个比,谁更高就更新”最高的是谁”。注意存的是下标(位置)不是分数本身——因为我们最后要显示的是名字,得靠下标回students里查。
(Python其实有内置的max()函数,max(totals)直接给最大值。但用它只能拿到分数,拿名字还得再找一遍。自己写循环反而更顺手——工具顺手比工具高级更重要。)
输出效果
请选择:3
全班平均分:79.2
最高分:小红(总分273)
最低分:小刚(总分185)
七、第二个改进:按成绩排名
考试完最想知道的就是排名。用第15篇学过的sorted():
def show_ranking(students):
"""按总分从高到低排名"""
if len(students) == 0:
print("还没有成绩记录")
return
# sorted不会改动原列表,返回新列表
ranked = sorted(students,
key=lambda s: s["语文"] + s["数学"] + s["英语"],
reverse=True)
print("n🏆 成绩排名:")
for i, s in enumerate(ranked):
total = s["语文"] + s["数学"] + s["英语"]
# 给前三名加个标记
medal = ""
if i == 0:
medal = "🥇"
elif i == 1:
medal = "🥈"
elif i == 2:
medal = "🥉"
print(f"第{i + 1}名 {medal} {s['姓名']:<8} 总分:{total}")
sorted vs sort
第15篇用过items.sort(...)。这里用sorted(students, ...),区别是:
列表.sort():原地排,列表自己变了sorted(列表):返回一个新列表,原列表不动
成绩排名只是”看一眼”,不该打乱内存里的原始顺序(万一以后要按录入顺序显示呢),所以用sorted更安全。
lambda这次长什么样
key=lambda s: s["语文"] + s["数学"] + s["英语"]
lambda s: ...的意思是”给我一个学生s,我返回他的总分”。按总分排,reverse=True从高到低。
前三名奖牌
if i == 0:
medal = "🥇"
elif i == 1:
medal = "🥈"
elif i == 2:
medal = "🥉"
纯粹是让输出好看的小彩蛋。emoji可以直接写在字符串里,print出来就是表情。
🏆 成绩排名:
第1名 🥇 小红 总分:273
第2名 🥈 小明 总分:255
第3名 🥉 小刚 总分:185
八、第三个改进:及格率统计 + 导出到文件
及格率
def pass_rate(students, pass_line=60):
"""统计三科全部及格的学生比例"""
if len(students) == 0:
print("还没有成绩记录")
return
passed = 0
for s in students:
# 三科都>=60才算及格(注意是and不是or)
if s["语文"] >= pass_line and s["数学"] >= pass_line and s["英语"] >= pass_line:
passed = passed + 1
print(f" ✓ {s['姓名']}(三科全部及格)")
else:
# 找出哪科没及格
failed = []
if s["语文"] < pass_line:
failed.append("语文")
if s["数学"] < pass_line:
failed.append("数学")
if s["英语"] < pass_line:
failed.append("英语")
print(f" ✗ {s['姓名']}({'、'.join(failed)}未及格)")
rate = passed / len(students) * 100
print(f"n及格率:{passed}/{len(students)} = {rate:.0f}%")
两个亮点:
and连条件:三科都及格才算——第五篇学的逻辑运算符。如果写成or(任一科及格就算),及格率会高得离谱,逻辑就错了。
'、'.join(failed):join是split的反操作!'、'.join(["语文", "数学"]) → "语文、数学"。把列表拼成字符串。第15篇split拆词,今天join合词,一对好搭档。
导出到文件(csv格式)
def export_to_file(students, filename="成绩单.csv"):
"""把成绩导出成csv文件,可以用Excel打开"""
with open(filename, "w", encoding="utf-8-sig") as f:
# 先写表头
f.write("姓名,语文,数学,英语,总分,平均分n")
# 一行写一个学生
for s in students:
total = s["语文"] + s["数学"] + s["英语"]
avg = total / 3
f.write(f"{s['姓名']},{s['语文']},{s['数学']},{s['英语']},{total},{avg:.1f}n")
print(f"已导出到 {filename}(可用Excel打开)")
csv格式:字段用逗号分隔的纯文本,Excel认识它。双击csv文件就打开一张表格——领导要的”成绩单”直接发过去。
encoding="utf-8-sig":比普通utf-8多存一个BOM标记,Excel打开中文csv不乱码的关键(Excel默认认GBK,有了BOM它就知道是UTF-8)。这个细节不知道就会踩坑,现在你知道了。- 表头单独写一行,数据一行一个学生,
n换行。
姓名,语文,数学,英语,总分,平均分
小明,85,92,78,255,85.0
小红,90,88,95,273,91.0
小刚,60,55,70,185,61.7
完整菜单
把所有功能接进主循环:
students = load_scores()
print("=== 成绩管理器 ===")
while True:
print("n1. 录入成绩 2. 查看全部 3. 统计信息")
print("4. 成绩排名 5. 及格率 6. 导出文件 7. 退出")
choice = input("请选择:")
if choice == "1":
name = input("姓名:")
try:
chinese = int(input("语文成绩:"))
math = int(input("数学成绩:"))
english = int(input("英语成绩:"))
except ValueError:
print("成绩必须是数字!")
continue
students.append({"姓名": name, "语文": chinese,
"数学": math, "英语": english})
save_scores(students)
print(f"已录入 {name} 的成绩")
elif choice == "2":
show_all(students)
elif choice == "3":
stats(students)
elif choice == "4":
show_ranking(students)
elif choice == "5":
pass_rate(students)
elif choice == "6":
export_to_file(students)
elif choice == "7":
print("再见!")
break
else:
print("无效选择,请输入1-7")
一个完整的”数据管理程序”成型了。每个功能一个函数,主循环只负责调度——菜单程序的黄金结构。
九、回顾一下用到了什么
| 知识点 | 在哪里用的 |
|---|
|——–|———–|
| 字典 | 每个学生的成绩数据 |
|---|---|
| 列表 | 全班学生的集合 |
| 列表套字典 | 整个数据结构 |
| json模块 | 数据存取(dump/load) |
| try/except | 文件不存在、输入非数字 |
| while True菜单 | 主循环 |
| append | 添加学生 |
| sum/len | 求和、计数 |
| 记录下标法 | 找最高分最低分 |
| sorted+lambda | 按总分排名 |
| and条件 | 三科全部及格判断 |
| join | 把科目列表拼成字符串 |
| 文件写入 | 导出csv |
| f-string格式化 | 对齐、保留小数 |
14个知识点,全部学过。 你没学”数据库”、没学”类和对象”,但做出的东西和很多真实的成绩管理系统,核心逻辑是一回事。
十、今日小练习
试着自己加一个功能(可以问AI帮忙):
功能:删除和修改学生记录
菜单加两个选项:
- “删除学生”:输入姓名,找到后从列表里删掉,保存
- “修改成绩”:输入姓名,找到后重新录入成绩,保存
提示:你需要列表的remove()方法,或者”找到下标再pop()“。找人的循环你已经会写了。
想想一个边界情况:输入的姓名不存在怎么办?程序该说什么?(提示:循环找完没找到,就打印”查无此人”。)
下一篇:待办事项工具 — 你自己的第一个”正经软件”:添加任务、标记完成、按优先级排序、未完成数量提醒。它是本系列的收官项目,会用到你学过的所有东西。
发表回复