我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Python第二次作业实战:学生信息管理系统的结构化编程与JSON持久化

Python第二次作业实战:学生信息管理系统的结构化编程与JSON持久化 1. 别急着写代码先想清楚这份作业到底在考什么拿到“Python第二次作业”这个题目时很多同学的第一反应是终于要写代码了赶紧打开编辑器试一试。但第二次作业往往不是第一次作业那样“打印个九九乘法表、算个闰年”的热身题了它开始真正区分“学过语法”和“能写程序”这两件事。我见过不少人第一次作业满分第二次作业却改到怀疑人生原因不是题目难而是根本没搞清楚这次作业背后的考核意图。第二次作业的典型指向是结构化编程。也就是说老师不再满足于你从上到下写一串能跑出结果的脚本而是希望看到你开始用函数组织代码、用文件保存数据、用异常处理应对不合法输入。如果你拿到手的题目里涉及“学生信息管理”“成绩统计”“通讯录”“图书登记”之类的小系统那基本可以确定考核点就是这几个流程控制是否熟练、函数封装是否合理、数据是否能持久化到文件、程序遇到意外输入时会不会崩。我当年带过的项目中有一大半同学在第二次作业丢分都丢在同一类问题上不是功能没实现是代码只在自己写的“标准输入”下能跑一旦换个调换格式、输入一个空行、文件里多了一个逗号程序就当场阵亡。这份作业表面上是写一个小管理系统实际上是在训练“健壮性”和“拆解能力”这两样东西而这两样恰恰是后边做任何实际项目都绕不开的基本功。还有一点需要提前说如果你拿到的是某某平台上的作业不要试图以“能提交通过”为唯一目标。提交通过只是及格线真正有价值的做法是把它当作一个迷你项目去打磨。因为第二次作业之后第三次、第四次作业的复杂度通常会阶梯式上升你现在偷的懒后面都得加倍还。2. 从分析题目开始这版学生信息管理系统到底要管什么2.1 第一次作业和第二次作业之间难度跃迁在哪里第一次作业大概率是让你输出一些计算结果或者用if-else处理几个判断分支。第二次作业命题时老师通常会引入“多数据 重复操作 文件落盘”这三件套。以最经典的学生信息管理为例它的核心需求通常包含这四块添加学生记录录入姓名、学号、成绩等信息查询/浏览记录按学号查一个人或者列出全部修改/删除记录更正错误数据或者移除已离校学生保存到文件下次启动能重新读入看起来功能不多但每一条都踩在“结构化程序设计”的关键节点上。你把代码写成一个平铺直叙的大循环也能跑通但当你试着把每个功能拆成独立函数时就会体会到模块化的好处主程序只负责调度每个函数只管一件事出问题的时候定位极快。2.2 我建议你先把数据结构定下来而不是先想界面很多人写这类作业时有个坏习惯先琢磨“用户按1做什么、按2做什么”急着搭菜单结果写到一半发现不知道用什么变量存数据。倒过来做会顺畅很多——第一步先确定数据怎么组织。学生这类实体最自然的方案是用字典。一个学号对应一个包含姓名和成绩的字典student { id: 2025001, name: 张同学, score: 88 }多个学生就是字典的列表或者用学号做外层字典的键students { 2025001: {name: 张同学, score: 88}, 2025002: {name: 李同学, score: 92} }两种结构都能用区别在于列表适合顺序遍历和保持插入顺序字典适合按学号随机查询查找时不用写循环。我个人更推荐第二种也就是以学号为键的字典结构因为第二次作业里“按学号查找、修改、删除”是高频操作字典直接一步到位而列表需要每次遍历。你写代码省事老师审查代码时也会觉得你思路清楚。成绩的处理上不要只存一个总分尽量把结构设计得有点层次感。比如语文、数学、英语单独存再提供一个计算平均分和总分的逻辑这样后面如果题目要求排序会非常方便。students { 2025001: { name: 张同学, scores: {语文: 90, 数学: 85, 英语: 88} } }2.3 搞清楚这版作业的“最小可运行版本”长什么样如果你现在完全没有头绪就先别想那些花哨的排序、统计、图表展示功能。先把最小可运行版本做出来这个版本只包含以下内容程序启动时尝试从文件读取已有数据进入一个while循环打印菜单选项用户输入1-5的数字分别对应添加、查询、修改、删除、退出每次操作结束后把内存中的数据写回文件输入不合法的选项时提示用户重新输入而不是直接崩溃这个骨架一旦跑通你的第二次作业就已经完成70%了。剩下的工作就是在这个骨架上加细节异常处理、输入校验、数据格式化。别一上来就想做得完美先让主流程转起来再逐层加固这是写任何程序都适用的节奏。2.4 为什么我建议你把文件格式定为JSON而不是CSV保存数据到文件是第二次作业的关键得分点。常见的做法有三类纯文本按行写、CSV逗号分隔、JSON结构化存储。纯文本最省事但解析麻烦比如你把“张同学,88”写进文件下次读的时候split(,)一下还能用但一旦成绩变成三门字符串里再混入中文逗号你就知道什么是人间地狱了。CSV比纯文本规范一点但它在处理嵌套结构时依然别扭——姓名、多门成绩拼成一行容易出错而且python自带的csv模块需要额外学一套读写接口。JSON是这三种里最适合这门作业的Python字典和JSON格式几乎是——对应的json.dump和json.load两个函数就完成了全部序列化工作。很多同学会说“老师没教过JSON”但这恰恰是第二次作业隐含的野心让你学会查文档、学会使用标准库中以前没见过的东西这种能力比背几十个语法点值钱得多。import json def save_to_file(students, filenamestudents.json): with open(filename, w, encodingutf-8) as f: json.dump(students, f, ensure_asciiFalse, indent4)注意两个参数ensure_asciiFalse保证中文写入文件后还是人话indent4让文件排版漂亮方便你调试的时候直接打开看。3. 代码骨架怎么搭把需求翻译成函数列表3.1 主循环、功能函数、工具函数三层拆分确定了数据结构和文件格式后就可以设计函数了。我不太建议把所有逻辑塞进一个超大while循环里虽然那样能跑但是一旦出bug你会陷入“从头读到尾也找不到问题”的痛苦。把功能拆成函数后每个函数独立测试问题范围瞬间缩小。一个清晰的主循环大概长这样def main(): students load_from_file() while True: show_menu() choice input(请选择操作: ).strip() if choice 1: add_student(students) elif choice 2: query_student(students) elif choice 3: modify_student(students) elif choice 4: delete_student(students) elif choice 5: save_to_file(students) print(再见) break else: print(无效选项请重新输入)这里有个细节值得强调每次操作后保存还是退出时统一保存两种策略各有道理。统一保存在“程序意外崩溃”时会丢失所有修改每步都存虽然多一点磁盘读写但对这种小体量的学生数据而言完全不是问题换来的是更安全。我建议采用每步都存理由很简单——第二次作业判分时助教可能会强制中断程序或乱按CtrlC如果数据没及时落盘最后的分数可能很难看。3.2 添加学生时输入校验比“能存进去”重要十倍添加学生是整套系统里最常被调用的功能也是最容易暴露输入问题的环节。学号空了怎么办成绩输入了英文字母怎么办分数超过100怎么办这些问题不加校验程序不会立刻崩但它会在后续的查询、排序、统计中向你索债。写一套基础校验并不复杂用异常处理配合循环确保每项输入合法后才写入数据结构def add_student(students): student_id input(请输入学号: ).strip() if student_id in students: print(该学号已存在请使用修改功能) return name input(请输入姓名: ).strip() if not name: print(姓名不能为空) return scores {} for subject in [语文, 数学, 英语]: while True: raw input(f请输入{subject}成绩: ).strip() try: score float(raw) if 0 score 100: scores[subject] score break else: print(成绩必须在0到100之间) except ValueError: print(请输入数字) students[student_id] {name: name, scores: scores} save_to_file(students) print(添加成功)这里有几个工程上的习惯值得你慢慢养成。第一输入后立刻.strip()去掉首尾空格避免用户手一抖多敲了个空格导致判断失灵第二成绩转换用float(raw)而不是int(raw)因为成绩可能有小数第三每个查询、添加操作都做“存在性检查”要么直接拦截要么给出明确提示绝不能静默覆盖。3.3 查询、修改、删除三个操作里最容易被忽视的边界情况查询看着简单按学号查一次输出信息即可。但别忘记一个场景查一个不存在的学号。如果不加判断程序会报KeyError而KeyError对用户来说就是“莫名其妙的崩溃”。修改和删除同理先检查学号是否存在再做操作这种防御式编程是第二次作业的重要得分点。删除功能还有个隐藏的坑删除后要把学生记录从字典里弹出但不能在遍历字典的同时修改字典——如果你用for循环遍历字典并从中删除元素Python会直接抛RuntimeError。正确写法是直接用pop(学号, None)第二个参数是默认值不存在时返回None而不是报错def delete_student(students): student_id input(请输入要删除的学号: ).strip() removed students.pop(student_id, None) if removed: save_to_file(students) print(删除成功) else: print(未找到该学号)修改功能则拆成两步先找到记录再修改其中的字段。修改时建议把“可修改的字段”限定在姓名和各科成绩上学号不要改——因为学号是字典的键改学号本质上得先删旧键再建新键处理起来容易出错而且现实中学生的学号也不会频繁变化。3.4 给代码加上最简单的“人味”让提示信息和输出排版可读作业的验收常常是人眼检查加运行测试输出可读性直接影响观感。存数据时用了JSON打印时也得把数据格式化得漂亮一点。别直接print一个字典那输出挤成一坨老师看着头疼你自己调试时也慢。可以用循环逐行打印或者构造一个简单表格def print_student(student_id, info): print(f学号: {student_id}) print(f姓名: {info[name]}) total sum(info[scores].values()) avg total / len(info[scores]) for subject, score in info[scores].items(): print(f {subject}: {score}) print(f 总分: {total:.1f} 平均分: {avg:.1f}):.1f是格式化小数位的写法保留一位小数平均分不会出现一长串无意义的小数尾数。这个输出不算高级但足够清爽评委一眼就能看清有没有实现“统计总分平均分”这个加分需求。4. 文件读写这关藏着第二次作业80%的失分点4.1 文件不存在的处理第一次启动时别让程序当场崩溃第一次运行程序时数据文件还不存在。如果直接调用json.load(open(students.json))FileNotFoundError会让程序当场退出这对用户来说可是灾难体验。正确的做法是判断文件是否存在不存在就返回空字典import os def load_from_file(filenamestudents.json): if not os.path.exists(filename): return {} with open(filename, r, encodingutf-8) as f: try: return json.load(f) except json.JSONDecodeError: print(数据文件损坏将使用空数据启动) return {}4.2 文件损坏这种极端情况怎么用异常处理优雅兜底上面代码里我顺手加了一个json.JSONDecodeError捕获。这个异常处理是什么场景就是数据文件被人为改过、或者上次写入时程序被强行终止导致文件内容不完整。对第二次作业来说这个try-except可能显得“过度设计”但它确实能说明你对异常处理的理解已经超过及格线。老师看到这段代码大概率会认为你具备处理真实项目问题的意识而不仅仅是为了交差。在真实工作场景里文件读写几乎每一步都可能出错磁盘满了、权限不足、编码不对、文件被其他地方占用。把这些异常都捕获并且在程序层优雅处理是“能用的代码”和“好用的代码”之间的一道重要分水岭。当然第二次作业不用全都处理但至少把文件不存在和JSON解析失败这两类最典型的场景覆盖掉这个思路已经足够。4.3 防止中文写入乱码的关键设置encodingutf-8这个坑几乎每个写文件的新手都会踩一次。Windows系统上如果不显式指定编码open默认的编码可能是GBK如果你在macOS或Linux上写文件时用了UTF-8换到Windows上打开文件中文就会变成一串乱码。解决方式是在所有open()调用中都显式写上encodingutf-8。这不是可选的“加分项”而是必须养成的习惯。第二次作业中涉及文件读写的同学里因为乱码被扣分的不在少数。你写得规范不仅是给自己省事也是在向审阅者传递一个信号你知道编码问题的存在并采取了防范。5. 把数据“玩起来”从基础功能到拉高分的排序与统计5.1 平均分排序看似简单但要照顾到分数数据结构变化如果题目允许你发挥或者你想让第二次作业的完成度显得更有诚意加一个排名功能是性价比很高的选择。按平均分从高到低排序核心代码用Python内置的sorted加key函数即可def rank_students(students): ranked sorted( students.items(), keylambda item: sum(item[1][scores].values()) / len(item[1][scores]), reverseTrue ) for i, (student_id, info) in enumerate(ranked, start1): avg sum(info[scores].values()) / len(info[scores]) print(f第{i}名: {info[name]} ({student_id}) 平均分 {avg:.1f})这一小段代码展示了三个知识点sorted函数的key参数如何使用、lambda表达式、enumerate的起始索引用法。如果你在第二次作业里能写出这样的代码说明你对Python的“函数式风格”已经有初步触觉了这在同学中是挺亮眼的表现。5.2 单科成绩最高分/最低分一道5行代码但考察细心的小题统计单科最高分和最低分也是常见加分项。实现思路很简单遍历所有学生取出某门科目的成绩用max/min函数求极值。但有个细节有的学生可能缺考某一科如果该科成绩不存在访问时就会KeyError。所以你需要在取值时用get()方法并给一个默认值或者提前过滤掉没有该科成绩的学生def subject_stats(students, subject): scores [info[scores][subject] for info in students.values() if subject in info[scores]] if not scores: print(f暂无{subject}成绩数据) return print(f{subject}最高分: {max(scores)}) print(f{subject}最低分: {min(scores)})注意列表推导式里那个if subject in info[scores]这就是防御式编程的体现。成绩记录可能不完整程序不能因为某条数据缺失就罢工。5.3 成绩区间筛选如何用几个if条件拼出一个“不笨”的查询再往外延伸一点你可以做一个“按分数区间筛选学生”的功能。传入最低分和最高分返回所有平均分在这个区间内的学生。这个功能写起来不复杂但能把你对循环、判断、数据提取的综合运用能力展示出来而且它天然适合作为第二次作业的扩展题“查询成绩在80到90之间的学生有哪些”的解法。def filter_by_score_range(students, low, high): results [] for student_id, info in students.items(): avg sum(info[scores].values()) / len(info[scores]) if low avg high: results.append((student_id, info[name], avg)) return results这些扩展功能加不加取决于你的时间和精力。我的建议是如果基础功能已经稳定加一个排序和一个区间筛选会明显提升作业的完成度和区分度也让期末的综合成绩有更多缓冲空间。6. 实测中翻车的三个经典场景排查链路完整复盘6.1 场景一程序一运行就FileNotFoundError文件到底去哪了这是最高频的翻车现场。现象运行python main.py程序直接抛文件不存在的异常。新手的第一反应往往是“我明明写对了啊”然后开始怀疑人生。排查过程是这样的。先看程序的工作目录os.getcwd()。如果你在A文件夹里启动了Python但代码里写的是相对路径students.json那这个文件是相对于A文件夹而言的。很多同学习惯双击运行脚本但脚本所在目录和当前工作目录不一致的时候相对路径就会找错位置。解决方式有两条一是把数据文件路径改为使用绝对路径不过这不太优雅二是在读写文件之前先把工作目录切到脚本所在目录import os base_dir os.path.dirname(os.path.abspath(__file__)) filename os.path.join(base_dir, students.json)但第二次作业如果只是让老师用命令行运行直接用相对路径也没问题。关键是要理解相对路径是相对于“当前工作目录”而不是“脚本所在目录”理解这一点比修好这个bug本身更有价值。6.2 场景二输入一个“abc”成绩程序疯狂报错退出输入非法字符导致崩溃是第二次作业里第二高频的翻车点。现象用户输入成绩时手抖打了“abc”程序抛出ValueError直接终止。排查思路是回到异常处理上。float(abc)会抛ValueError而ValueError没有被任何try捕获程序自然跑飞。修复方式就是前面代码里展示的while循环加try-except只要转换失败就提示重新输入直到拿到合法数字。这个小逻辑其实和你日常使用App的体验是一致的——你输错了App不会退出而是告诉你重新输入。把这种“人机交互”思路移植到命令行程序里就是一个合格的交互设计。6.3 场景三数据写入文件后中文变乱码怎么看都是“锟斤拷”乱码问题我们在前面提过根源就是编码不一致。现象在Windows上写入中文后重新读取发现文件里全是“锟斤拷”或者类似火星文。排查链路很简单查写入时用的open()有没有指定encodingutf-8查读取时用的open()有没有指定encodingutf-8如果你的代码里有些地方写了有些地方漏了混乱就在所难免。规范做法是把所有打开文件的入口都统一指定UTF-8编码不要依赖系统默认值。如果一次程序里有多处读写干脆定义一个统一函数读文件用load_from_file写文件用save_to_file其他代码不直接碰open()。6.4 这三个坑这么容易踩背后的共性是什么三个场景看似独立实际上指向同一个核心问题程序不能对用户的输入和运行环境做过于乐观的假设。文件可能不存在、输入可能非法、编码可能不匹配——这些都是异常情况而健壮的程序恰恰是在异常情况下仍能保持可用。把这种“悲观假设”内化成写代码的本能之后你写出的每一个程序质量都会有质的提升。第二次作业最大的价值就在于此它用一个小系统逼着你提前体验真实项目中最常见的那几类“意外”。7. 验收之前做一遍你自己的“检测清单”写完代码不是结束验收才是真正见真章的时刻。我强烈建议在提交之前至少走一遍下面这份检测清单你可以把它保存下来以后每次做项目都能用得上程序在数据文件不存在的情况下能否正常启动连续添加多个学生后重启程序数据是否还在成绩输入负数、大于100、纯字母时程序是否提示错误而不是崩溃查询、修改、删除一个不存在的学号时提示是否友好菜单选择输入“9”或其他非法选项时是否提示重试退出再启动后文件里的中文是否显示正常无乱码JSON文件手动打开后数据是否缩进清晰、结构可读所有open()调用是否都指定了encodingutf-8主逻辑是否拆成了函数而不是一坨冗长的顺序代码模块顶部的注释或docstring是否写清楚了程序功能和使用方式如果你逐条过完这些检查仍然没有发现致命问题那这份第二次作业的完成度就已经超过大多数同学了。别小看这份清单我见过有些同学代码写完了但从没重启过程序验证过持久化功能结果就是功能在单次运行里全对一重启全丢光最后得分惨淡。做检测时还有一个心得把自己当成一个什么也不懂的用户去用这个程序。不要按照你写代码时的理想输入来操作而是故意输入一些奇怪的值——空格、零、超长文本、带表情符号的字符串。程序能在这些“恶意”输入下保持稳定才算真正意义上的完成。8. 第二次作业之后怎么把这次经验“滚”成第三个项目的能力第二次作业的结束点其实是你编程能力起飞的一个拐点。如果你完整地把这个学生信息管理系统写完并调通那么你实际上已经掌握了构建几乎所有小型命令行应用的核心套路持久化存储、结构化数据、交互循环、异常处理。这三个半技能是后续写爬虫、写数据分析脚本、写自动化工具的底层能力。我自己的体会是做完第二次作业后可以尝试做一个小小的“升级挑战”把原来的命令行交互换成带简单界面的版本或者把存储从JSON换成数据库表格。这种增量式练习比“重新学一遍语法”效率高得多。它们会让你在不知不觉中理解“如何在一个已有系统上做扩展而不破坏原有功能”——这个能力在真实开发里比记住某个库的API重要得多。如果你还能再进一步试着给代码加上类型注解type hints和基本的函数注释def add_student(students: dict) - None: 添加新学生记录并保存到文件。这个习惯看似不起眼但它会让你的代码在半年后回看时仍然容易读懂。写代码和写文章一样清晰永远是第一位的。第二次作业教给你的不只是一个JSON怎么读写而是一种“面向健壮性和可维护性去设计”的思维方式。把这次作业当成一块跳板而不是一个通关任务你会走得更远。
返回列表