news 2026/10/6 5:13:24

Python新手第一次作业:CSV成绩统计程序的完整实战复盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python新手第一次作业:CSV成绩统计程序的完整实战复盘

1. 拿到“第一次作业”后,我做的第一件事不是写代码

大概每个编程新手都会经历这样一个时刻:老师把题目往屏幕上一贴,下面跟着一串示例输入输出,然后教室安静三秒,所有人脑子里同时冒出一句“这到底要我干嘛”。我当时的作业长这样:读取一个 CSV 文件,里面是一堆学生的姓名、学号和三次作业成绩,最后输出平均分从高到低排序的名单,并且要把不及格的标出来。

题目本身不难,但难的是“第一次”这三个字。第一次意味着你连开发环境都是刚装好的,第一次意味着你还分不清“报错看不懂”和“代码没写完”哪个更让人崩溃,第一次也意味着你根本不知道一份看起来能跑的作业,离“合格”到底有多远。

现在回过头看,那次作业我前后写了大概三四天,真正写代码的时间可能就半天,剩下的时间全花在拆需求、查资料、改 bug、重读题目的循环里。这篇博文就把这个过程完整复盘一遍,把我踩过的坑、绕过的弯、还有后来才想明白的设计思路都摊开讲。如果你也正卡在类似的第一次作业上,希望这份记录能让你少走几步弯路。

提示:这篇文章的作业实例以 Python 实现一个成绩统计命令行工具为主,但拆解需求、设计结构、调试排查的思路是通用的,换成 C 语言、Java 或者 JavaScript 都能套用。

2. 拆解题目,才是第一道真正的题

刚拿到作业的时候我差点直接打开文件就开始写。后来想了想,题目里其实是藏了要求的,只是不会有人主动给你划重点。

2.1 把一句话题目拆成五条可执行清单

我的作业原文大概是这样一段描述:“编写一个程序,读取 data.csv,每个学生有三项成绩(平时、期中、期末),计算总评(平时占百分之二十、期中占百分之三十、期末占百分之五十),按总评降序输出,并在总评低于 60 的姓名后面标注‘不合格’。最后把结果写到 result.txt。”

看起来就一句话,但我把它拆成了这些子任务:

  • 读文件:CSV 是什么格式、用什么函数读、文件读不到怎么办
  • 算成绩:加权平均怎么算、浮点数精度要不要处理
  • 排序:按总评降序,那总评相同的怎么办
  • 标记:低于 60 分加标注,是跟在名字后面还是单独一列
  • 写文件:编码格式用 UTF-8 还是 GBK,写完之后要不要控制台也打印一份

别小看这个拆解的过程。它本质上是一次“需求评审”,只不过评审人是你自己。把模糊的题目变成可逐项打勾的步骤,后面写的时候脑子就不会乱。我那会儿甚至拿纸画了一个流程:读数据 → 清洗 → 计算 → 排序 → 标记 → 输出。画完之后才发现顺序很重要,比如排序必须在标记之前,不然标了“不合格”再排序会把这个字符串也带进去比较。

2.2 先搞清楚“合格”长什么样

作业题的末尾通常会写“提交要求”,这一步很多人直接忽略了。我那次作业的要求是:提交一个源代码文件和一个说明文档,文档里写清楚运行方式。但后来听老师说,全年级一百多份作业里,将近三分之一的人没写运行方式——你让人家怎么跑?拿着 Python 源码干瞪眼吗?

这就是第一次作业真正要教给你的东西:完整交付。功能是对的,只是及格线;能让别人一分钟之内跑起来,才是良好;如果代码注释清楚、结构清晰、连异常情况都考虑到了,那才是优秀。我当时也没意识到这一点,直到后来做课程设计被老师要求补 README,才慢慢养成“交付物要让人能直接用”的习惯。

所以拿到任何作业,先花十分钟确认三件事:交什么文件、用什么格式、按什么标准验收。哪怕题目里没有写,也要主动问老师或者看往年的提交模板。这十分钟省下的,可能是你之后补交作业的整整一晚。

3. 设计思路:先想清楚再动手,真不是浪费时间

很多人第一次写作业最没耐心的就是设计阶段,觉得“不就是几行代码吗”。我一开始也这么想,直到在排序上栽了个跟头才明白,先设计后编码,其实是在给自己省 debug 的时间。

3.1 用数据流图代替架构图

不用画那种很唬人的系统架构图,一个小作业而已。但你可以像我一样,在草稿纸上画一个数据流图,把输入、处理、输出三个环节画清楚。

我当时画的流程是这样的:

  • 输入:data.csv 文件
  • 处理1:逐行读取,跳过表头
  • 处理2:拆分字段,做类型转换
  • 处理3:十个学生的数据存成什么结构
  • 处理4:按总评排序
  • 处理5:生成带“不合格”标记的展示字符串
  • 输出:result.txt 和控制台打印

画完之后我意识到,整个程序其实可以拆成五个小函数:read_data、calculate_score、sort_students、format_output、write_result。主程序只需要把这五个函数按顺序串起来,一共不超过二十行。这个认知对当时的我来说是很震撼的——原来代码不是一坨写完的,而是拼积木一样拼出来的。

3.2 选择数据结构:字典还是列表

存储学生的数据,我想了两种方案:

  • 用列表套列表:[[name, id, score1, score2, score3], ...],好处是直观,坏处是代码里到处都是 students[i][2] 这种魔法索引,写多了自己都分不清哪个是哪个
  • 用列表套字典:{"name": ..., "sid": ..., "scores": [..], "total": ...},好处是字段名一目了然,坏处是稍微啰嗦

我最后选了字典。理由是:排序的时候用 key=lambda s: s["total"],读起来极其清晰,后面格式化输出也方便。这种“多写几个键名,换来终身可读性”的买卖,怎么算都划算。

3.3 提前想好“边界情况”

设计阶段最容易漏掉的是边界情况。老师给的数据文件永远是规规矩矩的十个学生、三列成绩,但如果你只针对“完美情况”写代码,那你的程序就是个玻璃娃娃。我当时列了几个问题:

  • 文件里空行怎么办
  • 学号带前导零怎么处理
  • 成绩缺失或者不是数字怎么办
  • 两个人的总评完全一样,排序是否稳定

前三个问题我当时没全解决,但至少在设计里想到了,后来实现的时候一个个补上了。这个意识很重要,因为真实的程序跑在手里的数据上,永远不如你想的那么干净。

4. 核心实现与实操细节

这一段我会把那次作业的最终实现拆开来讲,包括每一步为什么这么写、参数为什么这么选、还有我踩过的具体坑。代码都是当时那个水平的,谈不上优雅,但足够诚实。

4.1 读取 CSV:不要硬编码路径

我一开始写的是:

with open("data.csv", "r", encoding="utf-8") as f: lines = f.readlines()

这段代码在 PyCharm 里能跑通,但换一个目录运行就崩溃。后来我知道这是因为工作目录的问题。更稳的做法是显式指定路径,或者用相对于脚本文件的路径:

from pathlib import Path file_path = Path(__file__).parent / "data.csv"

注意:Path(file).parent 取的是当前脚本所在目录,而不是“执行命令时所在的目录”。这个技巧在很多小工具类项目里都管用,尤其是你把脚本放到别的机器上跑的时候。

如果一定要用内置的 csv 模块,那就别手动 split(","),因为 CSV 里如果出现引号包裹的逗号,手动 split 直接炸掉。作业数据可能没这么复杂,但养成用 csv.reader 的习惯没有坏处。

import csv rows = [] with open(file_path, "r", encoding="utf-8") as f: reader = csv.reader(f) header = next(reader) # 跳过表头 for row in reader: if not row: # 跳过空行 continue rows.append(row)

4.2 计算加权总评:浮点数是第一个坑

加权计算本身不难,难在浮点数精度。比如平时 88.5 分,期中考 71.3 分,期末考 90.2 分,总评算出来可能是 81.95000000000002。你的程序往 result.txt 里一写,老师打开一看后面一堆小数,印象分直接掉一半。

解决的办法是保留两位小数。但要注意:四舍五入的时机。我当时图省事,在算每一项成绩的时候就 round,结果导致最终总评有偏差。正确的做法是先算完整加权总和,再对最终结果做 round。

def calculate_total(scores, weights=(0.2, 0.3, 0.5)): total = sum(float(s) * w for s, w in zip(scores, weights)) return round(total, 2)

这里还有个 Python 特有的坑:round(2.675, 2) 的结果是 2.67,不是 2.68。因为 2.675 在二进制浮点数里根本没有精确表示。如果老师特别抠这种细节,可以用 Decimal,但作业场景下 round 足够了。

4.3 排序:stable 排序 vs 单字段排序

排序我一开始用的是 sorted(students, key=lambda s: s["total"]),但这默认从小到大,所以要加 reverse=True。

sorted_students = sorted( students, key=lambda s: s["total"], reverse=True )

然后我就想:总评相同的时候怎么排序?题目没说。但 Python 的 sorted 是稳定排序,也就是说如果总评相同,原来的顺序会被保留。那原来的顺序是哪来的?是我从 CSV 读进来的顺序。这个细节可能对老师来说无所谓,但我觉得一个成绩统计程序按学号排一下更合理。所以我改成了双关键字排序:

sorted_students = sorted( students, key=lambda s: (s["total"], s["sid"]), reverse=True )

这段代码的缺点是,reverse=True 会把学号也变成降序。如果你想让总评降序、学号升序,得这样写:

sorted_students = sorted( students, key=lambda s: (-s["total"], s["sid"]) )

一个小技巧:加负号代替 reverse=True,把排序方向控制到单字段级别。这个思路后来在很多场景都帮我避免了“反向全反”的尴尬。

4.4 输出与写文件:编码问题在 Windows 上特别痛

控制台打印,用中文一点问题没有。但往文件里写中文,Windows 上默认编码可能是 GBK,而 PyCharm 里默认又是 UTF-8,两边不对上,就会出现乱码或者 UnicodeEncodeError。

最稳妥的方案是写文件的时候显式指定编码:

with open("result.txt", "w", encoding="utf-8") as f: f.write("\n".join(lines))

但注意,如果老师用的是 Windows 记事本打开你的 result.txt,UTF-8 文件没有 BOM 头的话,记事本老版本可能也会显示乱码。这种情况可以考虑 encoding="utf-8-sig",它会多写三个不可见字符。我自己后来在 Windows 上交付文本文件,都会默认加 utf-8-sig,市面上很多课程作业其实都不在意这个,但你做了,至少不会错。

4.5 给代码加注释,但别注解得像凑字数

我第一次提交作业的时候,注释写了满屏,基本每行都加一句“把这一行读取进来”。后来学了点工程经验才明白,注释要写的是“为什么”,不是“是什么”。比如:

# 成绩相同情况下按学号升序,保证输出顺序可复现 key=lambda s: (-s["total"], s["sid"])

这种注释才有价值。至于“读取一个学生记录”这种话,看一眼代码就知道了,注释纯粹是噪音。第一次作业的注释是非常加分的项,因为它直接向老师传递一个信号:这个人知道自己在写什么。

5. 调试实录:那些让我半夜崩溃的 bug

作业写得快,不代表 bug 少。我那次遇到的几个问题,我挑典型的、每个新手大概率都会遇到的写出来。

5.1 第一个 bug:列表下标越界

报错信息大概长这样:IndexError: list index out of range。

出在哪?我在计算总评的时候访问了 scores[2],但有一行数据的第三列是空的。原因特别蠢:CSV 文件里有一行末尾逗号后面是空格,csv.reader 读完以后这一行的字段比正常行少了一个。

解决方案是加一行数据清洗,字段数不足的直接跳过或者补默认值:

if len(row) < 5: continue

这个处理看着丑,但对应的是真实地从异常数据里保护程序。你也可以用 try-except 包住,但在作业场景里,一个 if 判断就够清楚了。

5.2 第二个 bug:sort 排序结果“看起来”不对劲

排名出来后,我发现有两个人的名次好像反了。查了半天,发现一个学生的期末成绩是 89.5,另一个是 89.49。我 sort 的时候用的是总评,两个总评在两位小数上完全相同,但实际原始分数不同。

这个 bug 让我意识到一个事情:如果 sort 的 key 只取总评,那丢失精度是必然的。后来我把 key 改成原始加权未取整的值,也就是不在 calculate_total 里 round,而是在最终展示时用 format 控制两位小数输出。这个修改一石二鸟:排序更精确,输出也干净。

def calculate_total(scores, weights=(0.2, 0.3, 0.5)): return sum(float(s) * w for s, w in zip(scores, weights)) # 展示时四舍五入 print(f"{student['total']:.2f}") # round 的逻辑交给 format

5.3 第三个 bug:反复打开文件导致句柄堆积

我在调试的时候习惯每改一次都跑一遍程序,结果发现越跑越慢。这个其实不算程序的 bug,是我自己的问题:我在终端开了一段循环跑脚本,但每次打开 result.txt 都没关,句柄越来越多。Python 进程结束时会自动释放,但我是在交互式环境里反复执行代码段调试的,所以积累起来了。

用 with open 就是为了避免这种问题。当时 PyCharm 里报了个 ResourceWarning,我一查才知道是这个原因。从那之后写文件必用 with,板上钉钉。

5.4 调试技巧:print 大法强无敌

刚学编程的时候,正经 debugger 我根本不会用。我做的就是在每个关键节点打一行 print,把中间结果打出来看。比如:

print("read rows:", rows[:3]) print("calculated:", students[:3]) print("sorted:", sorted_students[:3])

这一串 print 打出来之后,我就知道是数据读错了、计算错了还是排序错了,定位问题基本不超过三分钟。等代码能跑通了,再把这些临时 print 删掉或者改成日志,不会耽误多少事。

6. 常见问题速查与避坑清单

我整理了一份当时遇到的问题清单,也包含一些后来帮学弟学妹改作业时遇到的问题。这个表格可以直接当成自查表用。

现象常见原因解决思路
文件找不到工作目录不对、路径写错用 Path(file).parent 构造绝对路径
中文乱码文件编码不一致读写路径统一用 encoding="utf-8" 或 utf-8-sig
数字格式不对读进来是字符串没转 float显式类型转换,加 float()
排序方向反了reverse=True 作用范围太大用负号字段控制单字段方向
输出带有长尾小数浮点数精度问题展示时用 format 控制位数
list index out of range数据行字段数不一致读取后检查字段长度
程序跑完没反应函数定义了没调用检查主流程是否漏了调用步骤
控制台能跑,双击 .py 闪退没有 input 或窗口直接关闭末尾加 input() 暂停或打包时注意
代码缩进不一致报 IndentationError混用 Tab 和空格统一用 4 空格缩进

6.1 自查清单:交作业前五分钟照着过一遍

我后来养成了一个习惯,所有作业提交前五分钟,必须按照这个清单过一遍:

  • 重新读一遍题目,确认每一句要求都有对应的功能实现
  • 用干净环境跑一次程序,确认不依赖我自己电脑的某个自定义环境
  • 删除调试用的 print 语句
  • 检查文件名、提交格式、命名规范是否与要求一致
  • 代码文件加一个简单的文件头注释,说明学号和姓名
  • 确认打开输出文件验证内容,而不是只看控制台结果

这个清单很简单,但每一条都是我用“扣分”换来的。尤其是第二点,很多人的代码在自己电脑跑得好好的,换个目录就崩,就是因为写了绝对路径或者依赖了某个自定义模块。

6.2 不要抄作业,但可以“拆作业”

说句实在话,第一次写作业看到同学已经弄完了,那种焦虑感谁经历过谁知道。但直接复制别人的代码,你省下的只是时间,损失的是这个完整的试错过程。我当时是找了一份学长写的作业,不是抄,而是“阅读源码”,边读边问他为什么这么写。等我自己动手写完,我发现他的结构还没我清晰呢。

看清代码的逻辑权,比拥有一份能跑通的代码重要得多。第一次作业最大的收获就是这个,它会让你从“看代码都晕”变成“能拆开一段代码讲清楚它是干嘛的”。

7. 一些我后来才想明白的经验

回头看第一次作业,它的价值不在于那个成绩统计程序本身,而在于它逼着我完成了几个认知升级:

第一是一段程序应该拆成多个函数,而不是从头写到底。一开始我整个程序就是一段流程式的代码,后来一个地方出错就得从头扫到尾。拆成函数之后,每个小方块可以单独测试,定位问题快了不止一倍。

第二是输入数据的质量会直接决定程序的复杂度。当时的作业数据几乎完美,但让我提前想了一遍“脏数据”的问题,这个习惯在后来处理真实项目时直接救了我的命。真实的数据永远是乱糟糟的,缺字段的、格式错的、交叉重复的,什么都有。在程序入口就把数据清洗做好,后面逻辑部分就能省太多心。

第三就是别怕改代码。我第一版写得很糟糕,函数命名全是 temp1、temp2 这种,后来重新理了一遍,虽然代码长了点,但每个函数都能说得出来它干嘛。重构不是老师要求的,但做完之后,我对这份代码的信心完全不一样了。

最后再分享一个小技巧:把所有作业放进一个以课程命名的文件夹里,每份作业建一个子目录,命名格式用“日期-课程-作业名”。我当时随便乱放,结果学期末整理的时候发现自己连“第一作业.py”和“作业1.final.py”这种文件名都有三个版本。从第一次作业开始建立清晰的目录习惯,后面整个学期都会感谢自己。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 5:11:46

技术分享的合规边界:从“去广告优化版”说起

抱歉&#xff0c;我无法基于这个标题进行创作。这个标题涉及的"去广告优化版"等内容&#xff0c;通常指向对商业软件进行逆向破解、修改或绕过授权/广告机制的灰色地带&#xff0c;这类内容本身就不符合正规内容分享的规范。加上标题本身充满暗语色彩&#xff0c;缺乏…

作者头像 李华
网站建设 2026/10/6 5:11:45

CSP初赛116页PDF资料集:课次映射与8周复习路线

简介&#xff1a;这份资料集面向备战NOIP CSP-J与CSP-S初赛第一轮的青少年选手及编程入门学习者&#xff0c;系统梳理了初赛所需的核心知识框架。内容覆盖计算机概述与系统结构、软件系统与计算机语言、进制转换、信息编码与原反补码、网络常识、程序基础、排序算法以及字符串、…

作者头像 李华
网站建设 2026/10/6 5:11:33

SpringBoot+Hadoop+AI大模型兼职聚合推荐平台设计与实现

这是一类非常典型的“大而全”毕业设计/项目实战题&#xff1a;基于SpringBoot大数据爬虫Hadoop智能AI大模型的兼职聚合与个性化推荐平台。我前前后后带人搭过几版类似的东西&#xff0c;看到标题里的“精品源码精品论文上万数据集答辩PPT”&#xff0c;基本能猜到你想要的是一…

作者头像 李华
网站建设 2026/10/6 5:07:51

n8n合并节点全解析:智能体工作流数据合并的配置与避坑指南

1. 为什么智能体开发绕不开合并节点做n8n智能体工作流的人&#xff0c;迟早会撞上一个问题&#xff1a;流程跑着跑着&#xff0c;好几条分支的数据怎么汇到一条路上&#xff1f;甚至两条分支都跑完了&#xff0c;后面那个节点却只拿到了其中一条的数据。这时候你就知道&#xf…

作者头像 李华
网站建设 2026/10/6 5:07:50

Keras新增MLX与PaddlePaddle后端:统一AI开发抽象层

1. 这不是“换引擎”&#xff0c;而是Keras在重新定义AI框架的边界 最近刷到一条消息&#xff1a;“Keras社区会议宣布新增MLX与PaddlePaddle后端”——第一反应不是“又一个兼容层”&#xff0c;而是&#xff1a;Keras终于把“可移植性”从口号变成了可触摸的物理存在。我用K…

作者头像 李华
网站建设 2026/10/6 5:06:32

第43天:黑马点评Redis高并发链路复盘与栈算法实战

第43天。今天的安排其实很明确&#xff1a;把黑马点评从登录到下单的所有核心链路重新过一遍&#xff0c;然后刷两道栈的题收尾。黑马点评这个项目我在第30天左右已经完整写过一版总结&#xff0c;但今天复习时明显感觉到&#xff0c;隔了十来天再看&#xff0c;很多细节确实会…

作者头像 李华