Python实战:从零构建命令行学生成绩管理系统

发布时间:2026/7/28 3:38:06
Python实战:从零构建命令行学生成绩管理系统 1. 项目缘起为什么我要自己写一个成绩管理程序作为一名在一线摸爬滚打多年的开发者我处理过不少和数据管理相关的需求。最近一位在中学任教的朋友向我吐槽他们学校还在用Excel表格手动录入、计算学生的期中、期末成绩每次统计平均分、排名、及格率都搞得焦头烂额还容易出错。他问我有没有什么简单好用的工具推荐。市面上当然有成套的教务系统但对于一个班级或一个年级的简单管理来说那些系统要么太臃肿要么需要付费要么就是不够灵活。我第一个念头就是用Python自己写一个。这听起来像是个“Hello World”级别的练手项目但真要把它做得实用、健壮、可扩展里面门道可不少。它几乎涵盖了小型软件工程的核心流程需求分析、数据结构设计、文件操作、用户交互、异常处理甚至还能延伸到数据可视化和打包分发。对于刚学完Python基础语法想找个项目练手的朋友来说这是一个绝佳的切入点对于有经验的朋友如何设计一个清晰、可维护的架构同样值得思考。所以我决定动手做一个并把这个过程记录下来。这不是一个简单的脚本而是一个结构相对完整、带有命令行交互界面的程序。我们将从零开始一步步实现学生信息的增删改查、成绩录入、多种统计计算以及数据持久化。我会重点分享我在设计数据结构、组织代码逻辑以及处理边界情况时的一些心得这些是很多教程里不会细讲但实际开发中又至关重要的事情。2. 核心需求分析与程序架构设计在动手敲代码之前我们必须先想清楚这个程序到底要干什么。盲目开始很容易导致代码结构混乱后期添加功能困难。基于我朋友的需求和我自己的理解我梳理了以下几个核心功能点学生信息管理至少需要记录学生的学号、姓名。学号应作为唯一标识。成绩科目管理程序应能适应不同的科目设置如语文、数学、英语而不是写死。成绩录入与修改为指定学生录入或修改某科成绩。数据统计与查询计算单个学生的总分、平均分。计算单科的全班平均分、最高分、最低分、及格率。按总分或单科成绩进行排名。查询指定学生的所有成绩及统计信息。查询指定分数段的学生。数据持久化程序关闭后数据不能丢失。我们需要将数据保存到文件中下次启动能加载。用户交互提供一个清晰的命令行菜单让用户老师能方便地选择要执行的操作。基于这些功能我们来设计程序的核心数据结构。这里有几个关键选择为什么用嵌套字典而不用列表类对于初学者或者追求快速实现原型使用字典dict是非常灵活的选择。我们可以设计一个以学号为键以另一个字典为值的核心数据结构。这个“值字典”里存放姓名、各科成绩等。# 核心数据结构示例 students { “S001”: { “name”: “张三”, “scores”: { “语文”: 85, “数学”: 92, “英语”: 78 } }, “S002”: { “name”: “李四”, “scores”: { “语文”: 90, “数学”: 88, “英语”: 95 } } }这种结构的优点是直观、易于JSON序列化方便保存到文件。当然如果项目规模扩大使用面向对象编程定义Student类会更清晰。但在这个项目中我们用字典足以清晰地表达逻辑。科目列表独立存储我们将科目列表单独保存为一个列表如subjects [“语文”, “数学”, “英语”]。这样当需要添加或删除科目时只需修改这个列表而不需要遍历每个学生去修改其scores字典的键。这是一种“数据与逻辑分离”的简单体现。数据持久化方案选择我们选择JSON格式。为什么不是CSV或者纯文本JSON可以直接序列化/反序列化Python的字典和列表与我们的核心数据结构完美匹配读写代码非常简单。CSV更适合表格型数据但处理我们这种嵌套结构就麻烦一些。程序的基本架构流程图如下文字描述程序启动从data.json文件加载已有数据到students字典和subjects列表。显示主菜单等待用户输入。根据用户选择调用相应的功能函数如add_student,input_score,calculate_statistics。功能执行完毕后通常会将更新后的students数据自动保存回data.json或提示用户保存。返回主菜单除非用户选择退出。3. 环境准备与项目初始化工欲善其事必先利其器。这个项目对Python环境的要求非常宽松Python 3.6及以上版本均可。我个人的开发环境是Python 3.8配合VSCode。如果你还在纠结环境配置这里有一些最直接的指引Python安装前往Python官网下载安装包安装时务必勾选“Add Python to PATH”这是很多新手踩坑的地方。安装后在命令行输入python --version能显示版本号即成功。编辑器选择VSCode是绝佳选择轻量且插件生态丰富。安装Python扩展后智能提示、调试、代码格式化等功能一应俱全。当然PyCharm作为专业IDE同样优秀社区版免费。项目目录新建一个文件夹例如GradeManager。在里面创建我们的主程序文件grade_manager.py以及用于存储数据的data.json文件初始可以为空{}。我们不需要安装任何第三方库Python标准库的json、os、statistics用于计算平均数、标准差等就完全够用。这保证了程序的纯净性和可移植性。让我们开始编写代码框架。首先定义一些全局变量和初始化函数。import json import os # 定义数据文件路径 DATA_FILE ‘data.json’ # 初始化全局数据结构 students {} # 核心学生数据字典 subjects [] # 科目列表 def load_data(): “”“从JSON文件加载数据”“” global students, subjects if not os.path.exists(DATA_FILE): # 如果文件不存在初始化空数据并保存 students {} subjects [“语文”, “数学”, “英语”] # 默认科目 save_data() print(f“数据文件 {DATA_FILE} 不存在已创建默认文件。”) return try: with open(DATA_FILE, ‘r’, encoding‘utf-8’) as f: data json.load(f) students data.get(‘students’, {}) subjects data.get(‘subjects’, []) print(“数据加载成功。”) except (json.JSONDecodeError, IOError) as e: print(f“加载数据文件失败: {e}。将使用空数据。”) students {} subjects [“语文”, “数学”, “英语”] def save_data(): “”“将当前数据保存到JSON文件”“” data_to_save { ‘students’: students, ‘subjects’: subjects } try: with open(DATA_FILE, ‘w’, encoding‘utf-8’) as f: json.dump(data_to_save, f, ensure_asciiFalse, indent4) # indent使文件更易读 print(“数据保存成功。”) except IOError as e: print(f“保存数据文件失败: {e}”)关键点解析global students, subjects在函数内修改全局变量需要使用global关键字声明。os.path.exists检查文件是否存在避免尝试读取不存在的文件。encoding‘utf-8’指定UTF-8编码这是处理中文的最佳实践能避免乱码问题。json.dump(…, ensure_asciiFalse, indent4)ensure_asciiFalse保证中文字符能正常存储而不是被转义成\u形式indent4让生成的JSON文件有缩进便于人工阅读和调试。异常处理文件操作打开、读取、写入和JSON解析都可能出错。用try…except捕获这些异常IOError、JSONDecodeError并给出友好提示而不是让程序崩溃。这是编写健壮程序的基本素养。4. 核心功能模块实现详解有了基础框架我们开始逐一实现核心功能。每个功能我都会封装成一个独立的函数这样主程序逻辑会非常清晰。4.1 学生信息管理模块这个模块负责学生的添加、删除、修改和查询基本信息。def add_student(): “”“添加一个新学生”“” student_id input(“请输入学号: “).strip() if not student_id: print(“学号不能为空”) return if student_id in students: print(f“学号 {student_id} 已存在”) return name input(“请输入姓名: “).strip() if not name: print(“姓名不能为空”) return # 初始化该学生的成绩字典所有科目成绩默认为None表示未录入 scores_dict {subject: None for subject in subjects} students[student_id] { ‘name’: name, ‘scores’: scores_dict } print(f“学生 {name}({student_id}) 添加成功。”) save_data() # 自动保存 def delete_student(): “”“删除一个学生”“” student_id input(“请输入要删除的学号: “).strip() if student_id in students: name students[student_id][‘name’] confirm input(f“确定要删除学生 {name}({student_id}) 吗(y/n): “).strip().lower() if confirm ‘y’: del students[student_id] print(“删除成功。”) save_data() else: print(“操作已取消。”) else: print(“该学号不存在。”) def modify_student_info(): “”“修改学生姓名”“” student_id input(“请输入要修改信息的学号: “).strip() if student_id not in students: print(“该学号不存在。”) return new_name input(f“请输入新的姓名 (原姓名: {students[student_id][‘name’]}): “).strip() if new_name: students[student_id][‘name’] new_name print(“姓名修改成功。”) save_data() else: print(“姓名未更改。”)实操心得输入验证对学号、姓名的输入做了简单的非空检查。在实际应用中你可能需要更复杂的验证如学号格式。操作确认对于删除这种危险操作一定要有二次确认环节这是良好的用户体验。成绩初始化添加学生时我将其所有科目成绩初始化为None。这比初始化为0要好因为0是一个有效的分数而None能明确区分“未录入”和“考了0分”这两种状态。在后续统计时我们需要过滤掉None值。4.2 成绩录入与修改模块这是老师最常用的功能需要清晰易用。def input_or_modify_score(): “”“录入或修改某学生某科成绩”“” student_id input(“请输入学号: “).strip() if student_id not in students: print(“该学号不存在。”) return student students[student_id] print(f“当前操作学生: {student[‘name’]}({student_id})”) print(“可选科目:”, “, “.join(subjects)) subject input(“请输入科目名称: “).strip() if subject not in subjects: print(“科目不存在请先通过‘管理科目’功能添加。”) return current_score student[‘scores’].get(subject) prompt f“请输入 {subject} 成绩” if current_score is not None: prompt f“ (当前成绩: {current_score})” prompt “: “ try: score_str input(prompt).strip() if not score_str: # 直接回车表示不修改 print(“未输入成绩操作取消。”) return score float(score_str) if score 0 or score 150: # 假设满分150可根据实际情况调整 print(“成绩应在0-150之间。”) return student[‘scores’][subject] round(score, 1) # 保留一位小数 print(“成绩录入/修改成功。”) save_data() except ValueError: print(“输入无效请输入数字。”)关键点解析用户引导打印出当前操作的学生和所有可选科目用户体验更好。科目验证检查输入的科目是否在预设的subjects列表中。当前值提示修改时显示原有成绩这是非常贴心的设计。输入处理与验证使用try…except捕获非数字输入。对成绩范围做了简单校验0-150。使用round(score, 1)保留一位小数符合常见的成绩记录习惯。允许直接回车跳过取消当前操作。4.3 数据统计与查询模块这是程序的“大脑”负责各种计算。我们将统计功能拆分成几个小函数。def calculate_student_total(student_scores): “”“计算单个学生的总分和平均分忽略未录入科目”“” valid_scores [s for s in student_scores.values() if s is not None] if not valid_scores: return None, None # 没有有效成绩 total sum(valid_scores) average total / len(valid_scores) return round(total, 1), round(average, 1) def calculate_subject_statistics(subject): “”“计算单科统计信息平均分、最高分、最低分、及格率”“” scores [] for stu_id, stu_info in students.items(): score stu_info[‘scores’].get(subject) if score is not None: scores.append(score) if not scores: return None, None, None, None avg_score sum(scores) / len(scores) max_score max(scores) min_score min(scores) pass_count len([s for s in scores if s 60]) # 假设60分及格 pass_rate (pass_count / len(scores)) * 100 return round(avg_score, 1), round(max_score, 1), round(min_score, 1), round(pass_rate, 1) def query_student(): “”“查询指定学生的详细信息”“” student_id input(“请输入学号: “).strip() if student_id not in students: print(“该学号不存在。”) return stu_info students[student_id] print(f“\n 学生信息 ) print(f“学号: {student_id}”) print(f“姓名: {stu_info[‘name’]}”) print(f“\n--- 各科成绩 ---”) for subject, score in stu_info[‘scores’].items(): score_display score if score is not None else “未录入” print(f“ {subject}: {score_display}”) total, average calculate_student_total(stu_info[‘scores’]) if total is not None: print(f“\n总分: {total}”) print(f“平均分: {average}”) else: print(“\n暂无有效成绩”) def show_ranking(): “”“按总分或单科成绩排名”“” print(“请选择排名依据“) print(“1. 总分”) print(“2. 单科成绩”) choice input(“您的选择 (1/2): “).strip() ranking_list [] if choice ‘1’: # 按总分排名 for stu_id, stu_info in students.items(): total, _ calculate_student_total(stu_info[‘scores’]) if total is not None: # 只排有成绩的学生 ranking_list.append((stu_id, stu_info[‘name’], total)) ranking_list.sort(keylambda x: x[2], reverseTrue) # 按总分降序 rank_title “总分排名” score_column “总分” elif choice ‘2’: # 按单科排名 subject input(“请输入科目名称: “).strip() if subject not in subjects: print(“科目不存在。”) return for stu_id, stu_info in students.items(): score stu_info[‘scores’].get(subject) if score is not None: ranking_list.append((stu_id, stu_info[‘name’], score)) ranking_list.sort(keylambda x: x[2], reverseTrue) rank_title f“{subject} 成绩排名” score_column subject “成绩” else: print(“无效选择。”) return if not ranking_list: print(“暂无有效成绩进行排名。”) return print(f“\n {rank_title} ”) print(f“{‘名次’:5}{‘学号’:10}{‘姓名’:10}{score_column:10}”) print(“-” * 35) for idx, (stu_id, name, score) in enumerate(ranking_list, start1): print(f“{idx:5}{stu_id:10}{name:10}{score:10.1f}”) # 格式化输出对齐统计功能的设计思考忽略None值在计算总分、平均分、单科统计时我们都用列表推导式[s for s in scores.values() if s is not None]过滤掉了未录入的成绩。这是处理缺失数据的常见方法。函数复用calculate_student_total和calculate_subject_statistics被设计成纯函数只依赖输入参数不直接修改全局数据。这使得它们逻辑清晰、易于测试。排名算法我们使用Python内置的sort方法并指定key和reverse参数。lambda x: x[2]表示按元组的第三个元素即分数排序。这是一种简洁高效的排序方式。格式化输出在显示排名时我们使用了字符串的格式化方法f-string中的:10表示左对齐并占10个字符宽度让输出的表格更整齐美观。这是提升命令行程序用户体验的一个小技巧。4.4 科目管理模块为了让程序更灵活我们允许用户动态添加或删除科目。def manage_subjects(): “”“管理科目列表”“” while True: print(f“\n当前科目列表: {‘, ‘.join(subjects)}”) print(“1. 添加科目”) print(“2. 删除科目”) print(“3. 返回上级菜单”) choice input(“请选择操作: “).strip() if choice ‘1’: new_subject input(“请输入要添加的科目名称: “).strip() if new_subject and new_subject not in subjects: subjects.append(new_subject) # 为所有已存在学生的成绩字典添加新键初始值为None for stu_info in students.values(): stu_info[‘scores’][new_subject] None print(f“科目 ‘{new_subject}’ 添加成功并已为所有学生初始化。”) save_data() elif new_subject in subjects: print(“该科目已存在。”) else: print(“科目名称无效。”) elif choice ‘2’: if not subjects: print(“科目列表为空。”) continue subject_to_remove input(“请输入要删除的科目名称: “).strip() if subject_to_remove in subjects: confirm input(f“删除科目 ‘{subject_to_remove}’ 将清除所有学生该科的成绩确定吗(y/n): “).strip().lower() if confirm ‘y’: subjects.remove(subject_to_remove) # 遍历所有学生删除该科目对应的成绩键 for stu_info in students.values(): stu_info[‘scores’].pop(subject_to_remove, None) print(f“科目 ‘{subject_to_remove}’ 删除成功。”) save_data() else: print(“操作已取消。”) else: print(“科目不存在。”) elif choice ‘3’: break else: print(“无效选择请重新输入。”)重要注意事项数据一致性添加新科目时必须遍历students字典为每个学生的scores字典添加新键值为None否则查询该学生时新科目会显示为KeyError。删除科目时同样需要遍历所有学生从scores字典中移除对应的键。这是维护数据关联完整性的关键操作极易被初学者忽略。确认机制删除科目会永久删除所有学生该科的成绩因此必须有明确的警告和二次确认。5. 主程序循环与用户交互将所有功能模块整合起来形成一个完整的、可交互的程序。def display_menu(): “”“显示主菜单”“” print(“\n” “”*30) print(“学生成绩管理系统”) print(“”*30) print(“1. 添加学生”) print(“2. 删除学生”) print(“3. 修改学生信息”) print(“4. 录入/修改成绩”) print(“5. 查询学生信息”) print(“6. 显示成绩排名”) print(“7. 显示单科统计”) print(“8. 管理科目”) print(“9. 显示所有学生”) print(“0. 退出系统”) print(“”*30) def show_all_students(): “”“简要显示所有学生信息”“” if not students: print(“当前没有学生记录。”) return print(f“\n 所有学生列表 (共{len(students)}人) ”) print(f“{‘学号’:10}{‘姓名’:10}{‘已录入科目数’}”) print(“-” * 30) for stu_id, stu_info in students.items(): # 计算已录入成绩的科目数 recorded sum(1 for score in stu_info[‘scores’].values() if score is not None) print(f“{stu_id:10}{stu_info[‘name’]:10}{recorded}”) def show_subject_statistics(): “”“显示所有科目的统计信息”“” if not subjects: print(“暂无科目设置。”) return print(f“\n 各科成绩统计 ) print(f“{‘科目’:8}{‘平均分’:8}{‘最高分’:8}{‘最低分’:8}{‘及格率(%)’:10}”) print(“-” * 42) for subject in subjects: avg, max_s, min_s, pass_rate calculate_subject_statistics(subject) if avg is not None: print(f“{subject:8}{avg:8.1f}{max_s:8.1f}{min_s:8.1f}{pass_rate:10.1f}”) else: print(f“{subject:8}{‘-‘:8}{‘-‘:8}{‘-‘:8}{‘-‘:10}”) def main(): “”“主函数程序入口”“” load_data() # 启动时加载数据 print(“欢迎使用学生成绩管理系统”) while True: display_menu() choice input(“请输入选项 (0-9): “).strip() if choice ‘1’: add_student() elif choice ‘2’: delete_student() elif choice ‘3’: modify_student_info() elif choice ‘4’: input_or_modify_score() elif choice ‘5’: query_student() elif choice ‘6’: show_ranking() elif choice ‘7’: show_subject_statistics() elif choice ‘8’: manage_subjects() elif choice ‘9’: show_all_students() elif choice ‘0’: confirm input(“确定要退出吗(y/n): “).strip().lower() if confirm ‘y’: save_data() # 退出前自动保存 print(“感谢使用再见”) break else: continue else: print(“无效的选项请重新输入。”) # 每次操作后暂停一下让用户看清结果 input(“\n按回车键继续...”) if __name__ “__main__”: main()主程序设计的经验之谈清晰的菜单驱动这是命令行程序最经典的交互模式。每个功能对应一个数字简单直观。循环与退出使用while True构建主循环只有用户选择退出选项0时才通过break跳出。退出前再次调用save_data()是一个好习惯确保数据被保存。操作后暂停input(“\n按回车键继续...”)这行代码非常有用。它让程序在执行完一个功能后暂停等待用户按回车键才清屏或滚动显示主菜单。如果没有这个暂停菜单会立刻刷新用户可能来不及看清上一步操作的输出结果。if __name__ “__main__”:这是Python脚本的标准写法。它保证了当你直接运行这个文件时main()函数会被执行而当这个文件被作为模块导入到其他程序时main()不会自动执行。这是一种良好的编程实践。6. 功能扩展与进阶思路至此一个基础但完整可用的成绩管理程序已经完成了。但我们可以把它做得更好。这里分享几个进阶的扩展方向你可以根据自己的兴趣和需求尝试实现6.1 数据可视化使用Matplotlib统计数字是冰冷的图表更能直观反映问题。我们可以集成matplotlib库来生成成绩分布直方图、各科平均分柱状图等。首先需要安装matplotlibpip install matplotlib。然后可以添加一个功能函数import matplotlib.pyplot as plt def plot_score_distribution(subject): “”“绘制指定科目的成绩分布直方图”“” scores [] for stu_info in students.values(): score stu_info[‘scores’].get(subject) if score is not None: scores.append(score) if len(scores) 2: print(“数据不足无法生成图表。”) return plt.figure(figsize(10, 6)) plt.hist(scores, bins10, edgecolor‘black’, alpha0.7) # 分10个区间 plt.title(f‘{subject} 成绩分布直方图’) plt.xlabel(‘分数’) plt.ylabel(‘人数’) plt.grid(axis‘y’, alpha0.75) # 在图中标注平均分 avg_score sum(scores) / len(scores) plt.axvline(avg_score, color‘red’, linestyle‘dashed’, linewidth2, labelf‘平均分: {avg_score:.1f}’) plt.legend() plt.tight_layout() plt.show()在主菜单中加入调用这个函数的选项可以让老师快速了解全班在某科目上的成绩分布情况是正态分布还是两极分化一目了然。6.2 数据导入与导出CSV/Excel手动一个个添加学生和成绩效率太低。我们可以增加从CSV文件批量导入的功能。Python标准库csv可以轻松处理。import csv def import_from_csv(filename): “”“从CSV文件导入学生信息和成绩”“” # CSV格式假设第一行是表头如 “学号,姓名,语文,数学,英语” try: with open(filename, ‘r’, newline‘’, encoding‘utf-8-sig’) as f: # utf-8-sig处理BOM reader csv.DictReader(f) imported_subjects [col for col in reader.fieldnames if col not in [‘学号’, ‘姓名’]] # 更新科目列表去重合并 global subjects for sub in imported_subjects: if sub not in subjects: subjects.append(sub) count 0 for row in reader: stu_id row[‘学号’] name row[‘姓名’] if stu_id in students: print(f“警告学号 {stu_id} 已存在跳过。”) continue scores_dict {} for sub in subjects: # 尝试从行中获取成绩如果不存在或为空则为None score_str row.get(sub, ‘’).strip() scores_dict[sub] float(score_str) if score_str else None students[stu_id] {‘name’: name, ‘scores’: scores_dict} count 1 print(f“成功导入 {count} 条学生记录。”) save_data() except FileNotFoundError: print(f“文件 {filename} 未找到。”) except Exception as e: print(f“导入过程中发生错误: {e}”)同样也可以实现导出到CSV的功能方便用Excel进行二次处理或打印。这里的关键是处理好表头科目的动态匹配以及缺失值的处理空字符串转为None。6.3 将程序打包成可执行文件EXE你希望把这个程序分享给不会安装Python的同事使用吗使用PyInstaller可以轻松将Python脚本打包成独立的.exe文件。安装PyInstallerpip install pyinstaller在项目目录下打开命令行执行pyinstaller -F -w -i your_icon.ico grade_manager.py-F: 打包成单个exe文件。-w: 运行时不显示命令行窗口对于GUI或我们这种纯控制台程序有时需要保留窗口看打印信息可以先不加此参数调试。-i: 指定程序图标。打包后在dist文件夹里就能找到grade_manager.exe双击即可运行。需要注意的是程序运行时需要读写同目录下的data.json文件。打包后这个数据文件需要和exe放在同一目录下或者修改代码将数据文件路径设置为绝对路径或用户目录如os.path.join(os.path.expanduser(‘~’), ‘grade_data.json’)。6.4 使用SQLite数据库替代JSON文件当学生数量很大比如上千人时JSON文件加载到内存、查询、保存的效率会变低且缺乏事务等高级特性。此时可以考虑使用轻量级数据库SQLite。Python内置了sqlite3模块。你需要设计数据库表结构例如students表存放学号姓名scores表存放学号、科目、成绩然后将所有对字典的增删改查操作替换为SQL语句。这样做的好处是数据查询更灵活可以用复杂的SQL语句进行多条件筛选、连接查询性能更好数据一致性更有保障。当然代码复杂度也会相应增加。7. 项目总结与避坑指南回顾整个项目从需求分析到功能实现再到扩展思考我们完成了一个麻雀虽小五脏俱全的实用工具。在这个过程中我总结了几条非常重要的经验也是新手最容易踩坑的地方数据结构的深思熟虑是基石项目一开始花在思考students字典和subjects列表如何设计上的时间远少于后面写代码的时间。但正是这个清晰的结构让后续每一个功能的实现都变得顺理成章。如果一开始用列表存学生列表里再套字典查询某个学生时就需要遍历效率低下。在动手前多在纸上画画数据之间的关系。异常处理不是可选项是必选项文件读写、用户输入、类型转换处处都可能出错。一个健壮的程序不应该因为用户输入了一个字母而非数字就崩溃。全面的try…except和输入验证是程序从“玩具”走向“工具”的关键一步。用户体验藏在细节里显示当前成绩再让用户修改、删除操作前的二次确认、操作完成后的暂停提示、整齐的表格化输出……这些看似微小的设计极大地提升了程序的易用性和友好度。时刻站在用户老师的角度思考这个操作流程自然吗信息显示清楚吗函数化与模块化让代码清晰我们把每个功能都写成了独立的函数。这样做的好处非常明显主程序main逻辑极其清晰每个函数功能单一易于测试和调试未来要添加新功能只需要写一个新函数并在菜单里加个调用即可。避免写一个几百行的“超级主函数”。关于数据持久化的选择在这个项目中JSON是平衡简单性与功能性的最佳选择。它人类可读Python原生支持。但记住它不适合处理非常大量或需要复杂关系查询的数据。当你的需求升级时要知道有CSV、SQLite、乃至MySQL/PostgreSQL这些选项。这个项目代码总量不大但完整地走了一遍小型应用开发的核心流程。你可以直接使用它也可以以此为蓝本添加更多功能比如用户登录、权限管理、生成成绩报告单PDF、甚至是做一个简单的图形界面用Tkinter或PyQt。最重要的是通过亲手实现你真正理解了数据是如何流动的功能是如何被组织起来的。这才是“做项目”的意义远胜于死记语法。