Python入门实战:从零开发学生成绩管理系统(基础版)

Python入门实战:从零开发学生成绩管理系统(基础版) 1. 项目核心需求与功能拆解1.1 为什么第一课要选“学生成绩管理系统”很多刚接触Python的朋友都有个共同的困惑语法书翻了好几章循环、判断、列表也都能看懂但真让自己写点东西又不知道从哪下手。这个问题我见过太多次了。我的建议很简单——不要去做那些跟现实脱节的练习题直接挑一个你身边就存在的场景把它拆小用代码去实现。学生成绩管理系统就是绝佳的第一课。原因有几点第一它足够具体。分数、学生姓名、排名、平均分这些信息你从小就在接触不需要任何行业背景就能理解。写代码最怕的不是语法难而是需求模糊。你连问题都说不清楚怎么让计算机帮你解决第二它的功能可以无限扩展。基础版做一个录入和查询进阶版可以加入排序、统计、文件保存再往深了走还能接数据库、做图形界面、搞数据可视化。一个项目能吃透Python一大半的基础知识点而且每个阶段做完都有看得见的成果这种正反馈对新手来说特别重要。第三它是纯逻辑型项目不依赖任何第三方库。这意味着只要你装好了Python环境哪怕只用记事本写代码都能把整个项目跑起来。这对初学者的环境要求极低不会在配置环节就把热情消耗光。这套教案我实际带过好几轮有在校学生、有转行上班族、也有暑期自学的高中生。我发现一个规律凡是把成绩管理系统老老实实写完的人后面学函数、学文件操作、学面向对象都会顺很多。因为它把Python最核心的几块拼图——变量与类型、数据结构、流程控制、函数封装——全部串起来了。1.2 基础版到底做到什么程度既然叫“基础版”就要严格控制范围。我的建议是包含下面这些功能一个不多一个不少功能清单功能编号功能名称功能说明涉及知识点F1录入学生成绩手动输入学生姓名和三科成绩语文、数学、英语变量、类型转换、列表操作F2查看成绩列表按学号顺序显示所有学生的成绩for循环、格式化输出F3查询单个学生输入姓名显示该学生的成绩和平均分条件判断、字符串比较F4统计班级整体情况计算全班各科平均分、最高分、最低分循环嵌套、内置函数、列表推导式F5退出系统结束程序运行break、程序退出为什么只做这五项核心原因是它形成了一个完整的业务闭环——有输入F1、有输出F2、有查找F3、有分析F4麻雀虽小五脏俱全能让学生感受到“一个程序是怎么运转起来的”。而修改、删除、排序这些功能我故意放到第二版再做。原因很简单修改和删除涉及按条件定位数据需要引入索引和更复杂的列表操作排序则涉及算法思想。这些东西放到基础版里容易让初学者在“数据结构”层面卡壳而不是卡在“逻辑”层面。我在实际教学中发现很多人喜欢一上来就做一个超级完整的管理系统功能表列了一大堆结果写了两百行代码运行起来全是bug根本调不完。这不是教学这是劝退。项目越小完成率越高完成率越高兴趣越大。这个道理适用于任何编程新手。1.3 面向对象还是面向过程这个问题几乎每个学生都会问老师网上人家的成绩管理系统都用了class我是不是也得用我的回答是基础版绝对不要用类甚至不要用函数包太多东西就用最朴素的顺序结构加循环让代码一行一行的往下跑。为什么因为面向对象的核心是抽象和建模而初学者连数据怎么存都还没搞清楚一上来就搞class等于地基没打就盖二楼。面向过程的好处是直观程序从上往下执行遇到循环就反复执行遇到判断就分支跳转。数据流动的路径清清楚楚出了bug也容易定位。你可以把基础版代码写成一个大循环里面套几个if分支全文不到100行但逻辑非常完整。等到第二版、第三版需要处理更复杂的关系时再引入函数、再引入类学生会自然而然地理解“为什么需要这些语法糖”——因为他们自己已经踩过了代码重复维护的坑。这就是我常说的“按需学习”不为了用而用而是为了解决问题才去学一个新东西。基础版看到循环能省重复代码第二版看到函数能省重复逻辑第三版看到类能管理复杂状态。每个阶段都有明确的动机学起来一点也不痛苦。2. 环境准备与开发工具选型2.1 Python安装的坑与避坑写代码之前先把环境搞定这一步很多新手会卡住。网上安装教程满天飞但真正讲得清楚、不误导人的不多。首先明确一点本项目不依赖任何第三方库所以哪个Python版本都行。但为了后续系列教案的兼容性建议安装Python 3.10以上的版本。截止写这篇文章时Python 3.12、3.13都已经很稳定了直接装新版就好。安装时最关键的坑有两个第一个坑Windows系统安装时忘记勾选“Add Python to PATH”。这一步漏掉的话你在命令行窗口里敲python会提示“不是内部或外部命令”。装的时候一定要留意安装向导最底下那个复选框它在默认情况下是不勾选的需要你手动勾上。如果已经装完了才发现没勾也不用重装——去系统环境变量里把Python的安装目录和Scripts子目录手动加进去就行。第二个坑多个Python版本冲突。不少人的电脑里可能已经装了Anaconda或者之前装过其他版本的Python。这时候再装新版容易踩到版本错乱的问题。我的建议是如果是纯入门学习直接卸载掉之前装的乱七八糟的版本留一个干净的环境。等以后真的做数据分析、做爬虫了再上虚拟环境工具venv或conda去管理多版本。新手阶段不需要追求复杂方案越简单越好。装好之后怎么验证打开命令行窗口Windows是Cmd或PowerShellmacOS和Linux是终端输入下面的命令python --version如果输出类似Python 3.12.4这样的版本号说明安装成功。如果你敲python没反应但敲python3有反应说明系统把命令名区分开了后面所有代码里把python替换成python3执行即可。另外补充一句在命令行里进入交互模式直接敲python回车出现的界面可以拿来测试小段语法但对写整个项目来说效率太低。我们后面用脚本文件的方式代码写在.py文件里再用命令行整体执行。2.2 编辑器怎么选别纠结先用它选编辑器这件事我见过太多人陷入选择困难症VSCode还是PyCharmSublime还是Notepad要不要装各种插件我的态度很明确第一个项目选VSCode别的都不折腾。为什么不是PyCharmPyCharm功能确实强大但笨重启动慢而且专业版收费社区版虽然免费但有些功能阉割。对新手的第一个项目来说它那一堆配置选项反而成为负担。VSCode轻量、免费、跨平台、插件生态丰富做到学生成绩管理系统这个体量性能完全溢出。VSCode装好之后去扩展市场装一个官方Python扩展扩展名是“Python”由Microsoft发布。装完再用命令行验证一下环境路径python -m pip --version这里用python -m pip而不是直接pip的原因是为了确保pip和当前Python版本对应避免多版本环境下的工具错乱。虽然本项目不装任何包但养成这个习惯对以后有好处。然后新建一个文件夹比如叫student_management在VSCode里“文件 - 打开文件夹”选中它再新建文件main.py就可以开始写代码了。运行时有两种方式一种是在VSCode右上角点那个三角形的“运行”按钮另一种是在终端里执行python main.py。两种我都用过教学时更推荐后者——它让你清楚代码是在哪个目录下运行的对理解文件路径和相对导入有帮助。2.3 代码怎么写、怎么跑、怎么改编程入门阶段很多人有个误解以为写代码要一口气从头写到尾中间不能出错。这是大错特错。正确的节奏是每次只写一小段写完了立刻运行、立刻验证、立刻修正。代码是“改”出来的不是“写”出来的。比如先写个菜单打印的代码跑一下看看输出对不对再写录入功能的雏形跑一下手动录一条数据最后再把所有功能拼接起来。每个环节都验证过了最后的整体组合才能在极少报错的情况下一次性通过。具体到这个项目的写法我给学生的标准流程是这样的第一步画一个“纸上原型”——用文字描述程序运行时屏幕上应该出现什么界面用户按什么键会触发什么操作。这一步不需要代码但能帮你理清思路。第二步写框架——把主循环和菜单选项先做出来每按一个数字就打印一句话比如“你选择了录入成绩功能”。先不管具体实现只确保分支跳转正确。第三步逐个填充功能——先做最简单的录入再做查看再做查询最后做统计。每填充一个功能就跑一遍测试。第四步整体联调——把全部功能串起来从录入到查看再到统计完整走一遍业务流程。这个流程看起来慢实际是最快的。因为每步验证过了最后合成的bug数量会大幅减少而新手最怕的就是最后一次性合成时出现的“连锁报错”——这个错改了又冒出另一个错改完一个地方另一个地方又出问题很容易让人心态崩溃。3. 核心语法热身成绩系统需要哪些Python地基3.1 变量与数据类型成绩存成什么写成绩管理系统之前要先把几个最核心的语法点吃透。我不按教科书顺序讲而是按这个项目实际用到的顺序。第一个是变量。Python里变量不需要声明类型直接赋值就行。比如name 张三 chinese_score 88 math_score 92 english_score 75这里name是字符串类型三个分数是整数类型。Python会根据你赋的值自动推断类型。这个设计对新手极其友好不用像C语言那样先写一堆类型声明。这里有个比较隐蔽的坑input()函数返回的一定是字符串。也就是说如果代码写成score input(请输入成绩)那么score这个变量是字符串类型不是数字。你直接拿它做加减乘除会报错sum score 88 # 报错不能把字符串和整数相加解决办法是用int()做类型转换score int(input(请输入成绩))很多新手第一周就挂在类型转换上。所以我在教案里专门设计了一个小练习写三段代码分别演示不加转换、加转换、转换出错比如输入“abc”三种情况让学生亲眼看到报错信息长什么样。见过报错比背十遍知识点都管用。第二个是列表。一个班级有很多学生每个学生有多科成绩用什么结构来存我的建议是基础版用“列表里套字典”的结构students [] students.append({ name: 张三, chinese: 88, math: 92, english: 75 })外层列表负责管理多个学生内层字典负责存储单个学生的数据。字段名就是键成绩就是值。这种结构有三个好处一是语义清晰“students”是一个学生集合每一项就是一个学生二是扩展容易以后想加一科成绩直接加一个键值对就行三是打印到屏幕上时格式化输出也非常方便只要按字典的key取值。有的教材会推荐用二维列表列表套列表每个学生的信息按固定位置存放比如索引0是姓名、索引1是语文成绩。这种方案在数据量小时也能跑但有一个致命缺点代码可读性差。你看到一个students[i][2]我敢说隔三天你自己都记不清索引2代表的是数学还是英语。字典方案里写的是student[math]一目了然。基础版就用字典这个选择不亏。3.2 循环与条件菜单是怎么“转”起来的成绩系统的主界面是一个菜单用户选择一个数字执行一个功能执行完回到菜单直到用户选择退出。这个“反复执行、按条件退出”的形态靠的就是循环配合条件判断。Python最常用的是while True这种写法while True: show_menu() choice input(请选择操作) if choice 1: add_student() elif choice 2: show_all() elif choice 5: print(感谢使用再见) break else: print(输入无效请重新选择)这叫“死循环 内部跳出”的经典模式。while True表示条件永远为真所以循环会一直执行直到遇到break才跳出。菜单类程序几乎都是用这个模式写的。这里要专门讲一讲elif和else的顺序。程序从上往下判断命中了一个分支后剩下的就不再执行了。所以写的时候要注意第一个条件写“退出”还是写“录入”其实无所谓关键是每个分支的choice xxx要能对应上。还有一个新手特别爱犯的错菜单显示的是数字但input()返回的是字符串所以判断时要用if choice 1而不是if choice 1。前者是字符串比较后者是整数比较。如果你写成整数比较不管怎么输都不会命中因为类型不匹配。另外循环里还有一个重要的概念是“while循环的缩进”。Python用缩进来表示代码块的范围缩进错误是新手第二大军仅次于类型错误。我的建议是编辑器里统一用4个空格代替Tab键VSCode可以设置缩进为4空格。这样不管你换电脑还是在不同平台上打开代码格式都不会乱。3.3 函数封装让代码不堆成一坨基础版虽然以面向过程为主但完全不写函数也扛不住——菜单、录入、查询、统计这些功能如果都平铺在主循环里代码会超过100行到处都是if分支看着就头大。正确做法是每个核心功能封装成一个函数主程序只负责调用。def add_student(): name input(请输入学生姓名) chinese int(input(请输入语文成绩)) math int(input(请输入数学成绩)) english int(input(请输入英语成绩)) students.append({ name: name, chinese: chinese, math: math, english: english }) print(录入成功)函数的好处有三层第一层逻辑隔离。每个函数只做一件事出了bug直接定位到对应的函数排查范围瞬间缩小。第二层代码复用。比如“计算某个学生平均分”的逻辑在单个查询和全班统计里都要用写成一个calc_average(student)函数后两处都能调用不需要复制粘贴。第三层为纸上学到的“抽象思维”建立直观感受。当你看到主程序从一大团变成了几行简洁的函数调用你会真切理解“好代码长什么样”。封装函数时有个初学者容易忽略的点函数体内部的变量是局部的函数外部看不到。如果你在add_student()里定义了name函数外直接用会提示NameError。但有几种情况可以“绕过”这个限制比如在函数里修改传入的可变对象如列表这个修改会保留下来。上面代码里把新学生的字典append到全局列表students就是因为列表是可变对象函数内部修改全局列表是有效果的。这算Python的一个隐晦特性基础版可以先用起来原理以后学习作用域的时候再深挖。4. 分步实现从空文件到能用的系统4.1 第1步先搭起菜单框架不要着急写任何功能先把程序的主干搭出来。这一步只有三块内容导入模块、定义列表变量、写主循环。import sys students [] def show_menu(): print( * 30) print(学生成绩管理系统基础版) print(1. 录入学生成绩) print(2. 查看所有学生成绩) print(3. 查询单个学生成绩) print(4. 统计班级整体情况) print(5. 退出系统) print( * 30) def main(): while True: show_menu() choice input(请输入操作编号) if choice 1: pass # 以后填充 elif choice 2: pass elif choice 3: pass elif choice 4: pass elif choice 5: print(感谢使用再见) sys.exit(0) else: print(输入无效请重新选择) if __name__ __main__: main()如果你第一次接触if __name__ __main__:别慌这是Python里一个约定俗成的写法当这个文件被直接运行时执行main()当它被当作模块导入时不执行main()。现在你只需要记住每个Python程序的主入口都这么写就行。以后学了模块、学了包导入就会明白它的深意。还有那个pass关键字它表示“占位什么都不做”。这个技巧在搭框架阶段太有用了——你不需要一次性把功能写完可以先画好骨架再用后续步骤慢慢填肉。这一段的验证方式很简单运行程序看菜单是否正常显示输入1到5的每个数字看分支走向是否正确。注意现在选1不会有任何反应因为对应的函数还没写。下一步我就来填第一个功能。4.2 第2步实现录入功能把add_student()函数补全然后在主循环里把对应的pass替换成函数调用。这一步的关键点是类型转换和异常处理。def add_student(): print(--- 录入学生成绩 ---) name input(请输入学生姓名) try: chinese int(input(请输入语文成绩)) math int(input(请输入数学成绩)) english int(input(请输入英语成绩)) except ValueError: print(成绩必须是整数录入失败) return # 简单校验成绩范围 for score in [chinese, math, english]: if score 0 or score 100: print(成绩必须在0到100之间录入失败) return student { name: name, chinese: chinese, math: math, english: english } students.append(student) print(f学生 {name} 的成绩录入成功)这里有两个想重点讲的点。第一个是try...except。很多入门教案不讲异常处理直接用int()一转就完事。但我建议从第一个项目就养成防御性编程的习惯用户输入是不可信的你永远不知道对方会敲什么。try块里如果int()转换失败代码会抛出ValueError然后被except捕获程序不会崩溃而是显示一段友好提示。这个能力在真实项目里极其重要——用户把程序搞崩溃的方式有千万种代码必须能扛住常见的输入错误。第二个是成绩范围校验。虽然看起来是“多余的”但它能让学生理解一个概念程序不只是处理合法输入还要考虑非法输入。教师把成绩录成999或者-5这种明显不合理的数据程序必须拒绝。等以后学数据库、学后端开发你还会反复遇到“输入校验”这个话题基础版有这个概念打底后面会轻松很多。另外注意f{name}这种字符串格式化。前面加f大括号里写变量名运行时变量值会被拼进字符串里。这是Python 3.6的语法也是目前最推荐的做法。不要再用老式的姓名 name来拼接容易出错而且笨重。4.3 第3步实现查看与查询功能查看所有学生成绩的核心是遍历列表。这里我用一个enumerate()函数它可以同时拿到序号和数据def show_all(): print(--- 查看所有学生成绩 ---) if not students: print(当前系统里还没有学生数据请先录入。) return print(序号\t姓名\t语文\t数学\t英语\t平均分) for idx, stu in enumerate(students, start1): avg (stu[chinese] stu[math] stu[english]) / 3 print(f{idx}\t{stu[name]}\t{stu[chinese]}\t{stu[math]}\t{stu[english]}\t{avg:.1f}) def query_student(): print(--- 查询单个学生成绩 ---) if not students: print(当前系统里还没有学生数据请先录入。) return name input(请输入要查询的学生姓名) for stu in students: if stu[name] name: avg (stu[chinese] stu[math] stu[english]) / 3 print(f姓名{stu[name]}) print(f语文{stu[chinese]}数学{stu[math]}英语{stu[english]}) print(f平均分{avg:.1f}) return print(f没有找到姓名为 {name} 的学生。)show_all()里的if not students是一个很典型的判断空列表的写法。students是空列表时not students为真直接提示并返回。这个写法比len(students) 0更Pythonic也更简洁。query_student()的循环里用到一个技巧for...else。for正常循环结束后会执行else块但如果在循环体内用return提前退出else就不会执行。所以最后那行print(没有找到...)只有真的遍历完了还没找到匹配项时才会走到。这个结构比定义一个found标记变量再判断要简洁得多。这里有个潜在的坑如果系统里有两个同名“张三”query_student()只会返回第一个。解决这个问题的方法有两个一是录入时检查重名禁止重复二是查询时把所有同名学生都列出来。基础版我的建议是前者——在教学阶段先把流程跑通业务规则可以少一点但不能没有。4.4 第4步实现统计功能统计功能是最能体现“数据结构选对有多重要”的部分。因为学生成绩存在列表嵌套字典里用Python内置的sum()、max()、min()加列表推导式几行代码就能搞定。def show_statistics(): print(--- 统计班级整体情况 ---) if not students: print(当前系统里还没有学生数据请先录入。) return chinese_list [stu[chinese] for stu in students] math_list [stu[math] for stu in students] english_list [stu[english] for stu in students] print(f总人数{len(students)}) print(语文平均分 {:.1f}最高 {:.1f}最低 {:.1f}.format( sum(chinese_list) / len(chinese_list), max(chinese_list), min(chinese_list) )) print(数学平均分 {:.1f}最高 {:.1f}最低 {:.1f}.format( sum(math_list) / len(math_list), max(math_list), min(math_list) )) print(英语平均分 {:.1f}最高 {:.1f}最低 {:.1f}.format( sum(english_list) / len(english_list), max(english_list), min(english_list) ))[stu[chinese] for stu in students]是个列表推导式它的作用是把每个学生的chinese成绩提取出来组成一个新的列表。这个语法第一次看到可能觉得抽象但只要拆开读for stu in students是“遍历学生列表”stu[chinese]是取成绩一层层的都好理解。我不建议基础版里讲lambda、讲高阶函数但列表推导式值得提前接触。原因很简单它太常用了而且它的逻辑非常直白就是“对一个序列做映射转换”。把它和传统的for循环写法对比一下学生都会觉得推导式更清爽。等学熟了还能扩展出带过滤条件的写法比如[stu[name] for stu in students if stu[math] 90]这个能力后面做数据分析时会经常用到。还有一个细节平均值在Python 3中默认是浮点数除法/所以不用像Python 2那样担心整数除法截断的问题。但格式化输出时用{:.1f}保留一位小数会让界面清爽很多。这里的.1f表示“保留一位小数的浮点数格式”。4.5 第5步数据持久化——要不要存入文件基础版做到这里程序已经能跑了但会碰到一个问题每次运行程序数据都没了得重新录入。对于教学项目来说这个状态是完全可以接受的——因为还没有学到文件操作刻意不去处理数据存储的问题可以让学习者把注意力集中在逻辑上。但如果你带的学生基础不错或者学生自己提出了“下次打开能不能数据还在”的需求可以做一个简单的加分项用json模块把数据存到文件里。代码不长import json def save_data(): with open(students.json, w, encodingutf-8) as f: json.dump(students, f, ensure_asciiFalse, indent2) def load_data(): global students try: with open(students.json, r, encodingutf-8) as f: students json.load(f) except FileNotFoundError: students []然后在每次录入成功后调用save_data()在程序启动时调用load_data()。就这么十几行代码数据就能在多次运行之间保留。注意ensure_asciiFalse是为了让中文在文件里正常显示不然会变成一堆\uXXXX转义字符。indent2是让JSON文件格式化保存方便后期用文本编辑器查看。这个文件操作的知识点理论上应该在学完文件读写后再介入。但作为“顺水推舟”的扩展让感兴趣的学生提前接触反而能激发学习动力。如果学生还没学过文件操作可以暂时不做这一步。5. 完整代码与运行效果演示5.1 基础版完整参考代码把上面几个函数组合起来加上主菜单的调用完整代码大约100行左右。以下是我在实际教学中使用的版本你可以直接照着敲一遍也可以在此基础上改样式import sys students [] def show_menu(): print( * 30) print(学生成绩管理系统基础版) print(1. 录入学生成绩) print(2. 查看所有学生成绩) print(3. 查询单个学生成绩) print(4. 统计班级整体情况) print(5. 退出系统) print( * 30) def add_student(): print(--- 录入学生成绩 ---) name input(请输入学生姓名) try: chinese int(input(请输入语文成绩)) math int(input(请输入数学成绩)) english int(input(请输入英语成绩)) except ValueError: print(成绩必须是整数录入失败) return for score in [chinese, math, english]: if score 0 or score 100: print(成绩必须在0到100之间录入失败) return student { name: name, chinese: chinese, math: math, english: english } students.append(student) print(f学生 {name} 的成绩录入成功) def show_all(): print(--- 查看所有学生成绩 ---) if not students: print(当前系统里还没有学生数据请先录入。) return print(序号\t姓名\t语文\t数学\t英语\t平均分) for idx, stu in enumerate(students, start1): avg (stu[chinese] stu[math] stu[english]) / 3 print(f{idx}\t{stu[name]}\t{stu[chinese]}\t{stu[math]}\t{stu[english]}\t{avg:.1f}) def query_student(): print(--- 查询单个学生成绩 ---) if not students: print(当前系统里还没有学生数据请先录入。) return name input(请输入要查询的学生姓名) for stu in students: if stu[name] name: avg (stu[chinese] stu[math] stu[english]) / 3 print(f姓名{stu[name]}) print(f语文{stu[chinese]}数学{stu[math]}英语{stu[english]}) print(f平均分{avg:.1f}) return print(f没有找到姓名为 {name} 的学生。) def show_statistics(): print(--- 统计班级整体情况 ---) if not students: print(当前系统里还没有学生数据请先录入。) return chinese_list [stu[chinese] for stu in students] math_list [stu[math] for stu in students] english_list [stu[english] for stu in students] print(f总人数{len(students)}) print(语文平均分 {:.1f}最高 {:.1f}最低 {:.1f}.format( sum(chinese_list) / len(chinese_list), max(chinese_list), min(chinese_list) )) print(数学平均分 {:.1f}最高 {:.1f}最低 {:.1f}.format( sum(math_list) / len(math_list), max(math_list), min(math_list) )) print(英语平均分 {:.1f}最高 {:.1f}最低 {:.1f}.format( sum(english_list) / len(english_list), max(english_list), min(english_list) )) def main(): while True: show_menu() choice input(请输入操作编号) if choice 1: add_student() elif choice 2: show_all() elif choice 3: query_student() elif choice 4: show_statistics() elif choice 5: print(感谢使用再见) sys.exit(0) else: print(输入无效请重新选择) if __name__ __main__: main()这段代码我在多个班级里带过新手照着敲大约需要30到40分钟包括查错时间。敲完以后建议先跑一遍完整流程录入三名学生查看列表查询其中一个统计整体情况退出。不要跳过任一步骤手动走一遍才能发现逻辑上的bug。5.2 运行效果实测以下是我在Windows Terminal Python 3.12环境下的真实运行效果手动输入部分 学生成绩管理系统基础版 1. 录入学生成绩 2. 查看所有学生成绩 3. 查询单个学生成绩 4. 统计班级整体情况 5. 退出系统 请输入操作编号1 --- 录入学生成绩 --- 请输入学生姓名李小明 请输入语文成绩85 请输入数学成绩92 请输入英语成绩78 学生 李小明 的成绩录入成功 请输入操作编号2 --- 查看所有学生成绩 --- 序号 姓名 语文 数学 英语 平均分 1 李小明 85 92 78 85.0 请输入操作编号4 --- 统计班级整体情况 --- 总人数1 语文平均分 85.0最高 85最低 85 数学平均分 92.0最高 92最低 92 英语平均分 78.0最高 78最低 78注意查看列表时平均分显示为85.0因为{:.1f}强制保留了一位小数。最高 85和最低 85显示为整数是因为sum()、max()、min()返回的是原始整数类型。这种显示上的小差异不影响功能但如果你追求统一可以在格式化时改为.1f强制保留小数。另外我在跑测试时故意输入过“abc”来触发异常捕获请输入操作编号1 --- 录入学生成绩 --- 请输入学生姓名王五 请输入语文成绩abc 成绩必须是整数录入失败程序没有崩溃稳稳地回到了主菜单。这个体验对学生来说非常重要——一个不会被用户输入搞崩溃的程序才算得上一个“能用的程序”。5.3 遇到运行不了怎么办前置自检清单如果代码敲完了但运行报错先别急着上网搜。按照下面的顺序自查一遍能解决80%的问题文件名是不是以.py结尾如果你建的是文本文件.txt运行会报“No such file or directory”或者干脆找不到模块。缩进是不是一致Python对缩进极其敏感。VSCode里如果看到某个地方“明明看着对齐了但报错”试着选中整段代码按Shift Tab再重新缩进。中英文标点是不是混用了新手最爱犯的错是在input(请输入)里不小心用了中文冒号或者在变量名里混进了中文符号。代码里的引号、冒号、括号、逗号必须全部是英文半角状态。变量名是不是拼对了students和student是两个不同变量choise和choice也是两个不同变量。排查时注意看报错信息里提到的变量名和定义处是否完全一致。代码里有没有用中文输入法打过字如果注释或者字符串里有中文没问题但中文引号会直接导致语法错误。这一条和第三条本质是同一类问题。如果以上都排查过了还是报错那就把报错信息复制下来不是截图是文本去搜索引擎搜索报错信息本身不要搜“成绩管理系统报错”这种泛泛的内容。报错信息最后一行的TypeError、NameError、IndentationError就是定位问题的关键。6. 常见问题与排查技巧实录6.1 初学者最常踩的五个坑这部分内容来自我多年带新手的真实观察每一个坑都对应过真实的学员案例。我把它们整理成表格方便对照。排序报错/现象根本原因排查办法1NameError: name students is not defined变量定义位置不对或函数里用了还没定义的变量检查students []是否写在函数外部且在所有使用它的函数之前2ValueError: invalid literal for int()int()收到了非数字字符串看是否有int(input(...))而没有try...except保护3TypeError: can only concatenate str字符串和整数用拼接改用f-string或str()转换4IndentationError: unexpected indent缩进不一致某行多余了缩进全选代码统一用4空格缩进5程序一运行窗口就关闭代码报错程序崩溃退出在终端里运行而不是双击.py文件这样能看到完整报错信息第一条的细节值得展开说。Python里变量有作用域的概念如果在函数内部直接给students赋值、而不先声明它是全局的Python会把它当成一个全新的局部变量。但基础版代码里students.append()只是修改列表内容不是重新赋值所以反而是可以的。真正会踩坑的是像load_data()那种函数内部students json.load(f)的情况——那是在重新绑定变量如果不写global students就会报“local variable referenced before assignment”。等做到文件持久化扩展时学生大概率会遇到这个问题。第二个坑其实就是前面聊过的类型转换问题。很多人在交互式环境里测试int(abc)会看到ValueError但到了完整项目里却忘了去捕获这个异常。6.2 逻辑类问题的排查思路除了语法报错还会有一种更难缠的问题程序能跑但结果不对。这类问题没有报错信息只能靠推理和调试。举三个我在教学中实际遇到的案例**案例一查询不到输入的学生。**学生录入了“张三”查询时输入“张三”却说查不到。排查后发现是录入时带了一个空格“张 三”或者结尾多了个空格。解决方法有两个录入时用strip()去首尾空格或者查询时也用strip()。我两个都做了双保险。**案例二平均分计算不对。**有个学生写的是avg stu[chinese] stu[math] stu[english] / 3算出来的结果总是偏高。原因是运算符优先级Python先算除法再算加法等价于语文 数学 (英语 / 3)。必须用括号改成(语文 数学 英语) / 3。这个案例我每次都要讲因为它是“看起来对了但实际上错了”的典型。**案例三统计数据里出现了一个巨大的数。**运行统计功能时某科平均分飞出天际。最后发现是录入成绩时有个学生输成了9999而我的成绩范围校验只在录入时检查没在加载旧数据时检查。这也是为什么我强调“输入校验要做在最早入口处”否则脏数据会一路污染后续统计。排查这类逻辑问题我给学生的方法是“打印大法”在可疑的变量后面加print(repr(变量))看看到底存了什么值。repr()会把字符串里的空格、换行等不可见字符显示出来比直接print()更容易暴露问题。排查完记得把调试用的print删掉保持代码干净。6.3 调Bug的心法别怕错怕的是乱最后想说的是心态问题。新手第一次遇到一堆报错时最容易产生的念头是“我不适合编程”。但我要告诉你们一个事实专业程序员写代码一次通过的场景也不多。报错不是失败是程序在告诉你它哪里不舒服。我平时给学生的建议是三个词慢下来、分步骤、看信息。慢下来是指不要盯着一大段代码发呆或者反复重写那是最浪费时间的。先让代码停下来读一读报错信息它已经告诉了你文件路径、行号、错误类型。分步骤是指用注释或者# 临时停用的方式把功能拆开验证。比如怀疑录入函数有问题就在主循环里先注释掉其他分支只留录入和退出两个选项跑一下看看。看信息是指要学会从完整的报错堆栈里提取关键信息。很多人看到报错一长串就直接慌了其实只需要关注最底部那行“某文件第某行 错误类型 具体描述”。报错堆栈上层的行是调用关系在基础版这个体量里通常最底下一行就是问题所在。调试Bug这件事练多了会形成肌肉记忆。第一次踩坑可能要半小时第二次十分钟第三次一分钟。到后面你会发现报错信息就像老朋友打招呼——你甚至能猜到它接下来要说什么。这种熟练度靠看教程是学不来的必须自己动手填坑。7. 这套教案的扩展方向与后续安排基础版做完别停在这里这个项目本身就是一颗非常好的“种子”顺着几个方向都可以长出大树。方向一是功能扩展。加入修改、删除学生信息加入按单科成绩排序加入按姓名模糊查询加入多个班级的管理。这些功能每加一个都会引入一个新的Python知识点。比如修改功能要掌握“如何通过列表索引定位元素”排序功能要掌握sorted()的key参数模糊查询会用到in运算符。方向二是文件与数据持久化。把上一节提到的json存盘做完整甚至可以用CSV格式导出成绩单让Excel能直接打开。这一块是通往“真实项目”的关键一步——真实系统不可能每次启动都清空数据。方向三是可视化。等学到数据可视化库比如matplotlib之后回到这个项目把班级平均分画成柱状图把每个学生的三科成绩画成折线图瞬间高大上。而且因为数据源已经在列表里了接可视化基本上是水到渠成。方向四是面向对象重构。等掌握了类与对象可以把学生抽象成Student类把管理系统抽象成ScoreManager类。这个重构过程会让人深刻理解“面向对象不是炫技而是为了管理复杂度”。我自己在带系列教程时通常把这个基础版安排在第三周也就是学完变量、列表、字典、循环、函数之后。前面两周的语法学习是“点”这个项目就是第一次把“点”串成“线”。后续的扩展版则是把“线”织成“面”。回到开始的那个问题为什么成绩管理系统适合作为Python系列第一课因为它逼着你把刚学的东西全部用上而且用得很自然——列表存数据、字典存字段、循环遍历、条件分支、函数封装、异常处理没有任何一个知识点是“为了用而用”。当你把这个系统跑起来看到自己写的程序真的能帮忙记录、查询、统计成绩时那种“我能行”的感觉比任何教程里的赞美都管用。下一篇教案我计划在这个基础之上做“学生成绩管理系统进阶版”加入修改、删除、排序和数据持久化到时候基础版的代码大部分可以复用等于是让读者亲身体验一次“在旧项目上迭代”的真实开发节奏。各位先把基础版敲熟有问题随时在评论区交流。最后再分享一个小技巧写完代码后尝试不看源码自己白纸把整体流程画一遍——从主菜单开始每个分支会走到哪个函数函数内部又干了什么。能画清楚这张流程图说明这个项目你才是真的吃透了不是“照着抄完就忘”。这比刷十道练习题都管用。