学习前提
完成Python函数、异常和文件读取;能理解字典分组累加。
本课完成后你能做什么
串联解析、校验、分组与排序,生成可解释的批量统计报告。
先理解机制
先定义输入列course与minutes,每行代表一次学习记录;输出为课程总分钟数与错误行。不要在解析时立即print全部内容,让解析和统计分别可测试。本例用内存字符串提供完整样例,之后可把StringIO替换成UTF-8文件对象。
csv.DictReader负责逗号与引号规则,不能简单split逗号,因为被引用的课程名也可能包含逗号。校验列名与字段内容,分钟必须是0至240整数;失败记录进入错误报告,不参与合计。
用字典按课程累加,输出时按总分钟降序,再按课程名打破并列,保证可重复结果。错误报告使用源文件行号,方便学习者定位。部分成功策略需写清楚:本例保留有效行并报告无效行,不假装所有输入都合格。
项目扩展应围绕边界推进:空文件、缺列、重复记录、额外列、非法数字。首先决定重复记录是两次学习还是误导入,再实现去重;盲目按整行去重可能丢掉真实的重复学习。
可运行示例
使用Python 3.11或更新版本,将代码保存为独立练习目录中的lesson.py,在该目录执行 python3 lesson.py;Windows也可使用 py lesson.py。示例只使用标准库。
import csv
from io import StringIO
source = "course,minutes\nPython,25\nSQL,20\nPython,35\nJava,invalid\nSQL,-5\n"
reader = csv.DictReader(StringIO(source))
if reader.fieldnames != ["course", "minutes"]:
raise ValueError("Expected course,minutes headers")
totals = {}
errors = []
for row in reader:
course = (row.get("course") or "").strip()
try:
minutes = int(row.get("minutes") or "")
if not course or not 0 <= minutes <= 240:
raise ValueError("invalid row")
except ValueError:
errors.append(reader.line_num)
continue
totals[course] = totals.get(course, 0) + minutes
for course, minutes in sorted(totals.items(), key=lambda item: (-item[1], item[0])):
print(f"{course}: {minutes}")
print("Rejected lines:", errors)
推演与验证
输出Python60、SQL20,错误行5与6。将数据改成仅表头应没有课程行;把quoted课程名加入数据,验证csv处理引号。故意改变表头顺序会被当前严格规则拒绝,若希望支持任意顺序应修改规则并补测试。
常见错误与边界
示例未处理无限大文件和恶意长字段,不要把练习直接作为无边界上传接口。统计时长不是评价学习效果的唯一依据,报告只陈述输入数据汇总。
动手练习
把解析整理成summarize(stream)函数,返回totals与errors;增加按课程导出CSV,并验证导出后再读能保持数据。
参考解法与理由
计算函数不打印,入口负责展示;CSV写入交给writer,保持列名与顺序明确。测试有效记录、空输入和错误记录三组,错误报告应包含原因而不只有行号。