实验专题(一):从列表到字典——手把手重构你的成绩录入系统
实验专题一从列表到字典——手把手重构你的成绩录入系统当你的数据需要快速按“姓名”查找时字典是比列表更优雅的数据结构。本文基于《Python 学习笔记第五期——组合数据类型》中关于列表和字典的核心知识带你一步步将基于列表实现的成绩管理系统重构为基于字典的实现并详细解释每一步的设计思路与坑点。原实验Python实战手把手带你撸一个成绩管理系统附源码详细注释关联笔记Python 学习笔记第二期——基础语法下篇运算符全解与实战演练 —— §5 成员运算符in/not inPython 学习笔记第五期——组合数据类型列表、元组、集合与字典精讲 —— §2 列表、§5 字典、§8 可变与不可变类型更多Python知识Python基础入门我的主页AOwhisky这里有更多运维系统性知识整理和其他有趣内容欢迎与我一起探讨学习~ 实验目录缘起一个经典的小项目目标用字典重构数据结构功能模块逐一改写完整可运行代码列表 vs 字典对比总结避坑指南新手常犯的字典错误扩展思考如果允许重名怎么办知识点对应与总结一、缘起一个经典的小项目很多 Python 初学者都会写一个“成绩录入系统”来练习流程控制和数据结构。最初我们可能用列表嵌套列表来存储学生信息这也是第五期 §2.1中介绍的列表基本用法info[[刘备,90,88,95],[张飞,70,75,80]]这种结构简单直接但随着功能扩展它暴露出几个明显的短板问题说明影响查找效率低按姓名查找成绩需要遍历整个列表时间复杂度 O(n)数据量越大越慢代码可读性差通过下标i[0]、i[1]访问数据一眼看不出i[2]是数学还是英语维护成本高字段顺序调整时所有下标都要改动极易引入 bug数据语义模糊纯数字下标无法表达业务含义代码难以理解和维护而 Python 内置的字典dict——参见第五期 §5——天生就是为“键值对”查询设计的用姓名作为键成绩作为值既快又清晰。于是我决定将原系统用字典重写一遍并在此记录下完整的改造过程。二、目标用字典重构数据结构2.1 选择哪种字典结构在开始重构之前我们先回顾第五期 §5.1中介绍的字典创建方式。根据业务需求有两种常见设计方案外层键内层值优点缺点A学号自动生成{name:刘备, chinese:90, ...}支持同名学生查询时需要先遍历找姓名稍麻烦B姓名{chinese:90, maths:88, english:95}直接通过姓名访问O(1)极速不支持重名因为题目明确要求“姓名重复则无法录入”所以我选择方案B直接用姓名作为外层键。这样后面所有功能查询、修改、删除都可以用if name in info一行搞定——这正是第二期 §5中介绍的成员运算符in在字典中的典型应用。2.2 初始数据基于方案 B初始数据结构如下注意这里使用了字典嵌套字典的结构参见第五期 §5.3info{刘备:{chinese:90,maths:88,english:95},张飞:{chinese:70,maths:75,english:80}}数据结构可视化info (外层字典) ├── 刘备 (键) → {chinese: 90, maths: 88, english: 95} (内层字典) │ ├── chinese → 90 │ ├── maths → 88 │ └── english → 95 └── 张飞 (键) → {chinese: 70, maths: 75, english: 80} ├── chinese → 70 ├── maths → 75 └── english → 80三、功能模块逐一改写原系统有五个功能录入、查询全部/个人、修改、删除、退出。下面我们逐个用字典风格重写并在每段代码中标注对应的笔记知识点。3.1 成绩录入对应 第五期 §5.3原列表做法先检查姓名是否在列表中遍历若不在则构造列表[name, ch, ma, en]并追加。字典做法直接判断name in info第二期 §5成员运算符不存在则新增键值对第五期 §5.3字典元素的添加。ifselect1:nameinput(请输入姓名)ifnameininfo:# 成员运算符 in时间复杂度 O(1)print(姓名已存在请重新录入)else:chint(input(请输入语文成绩))maint(input(请输入数学成绩))enint(input(请输入英语成绩))# 字典添加新键值对第五期 §5.3info[name]{chinese:ch,maths:ma,english:en}print(f{name}成绩录入成功)核心优势in操作在字典中平均时间复杂度为O(1)远快于列表的O(n)。随着数据量增长这个差距会越来越明显。3.2 成绩查询对应 第五期 §5.23.2.1 查询全部成绩遍历字典的items()方法第五期 §5.2同时拿到姓名和成绩字典ifsub1:print(姓名\t语文\t数学\t英语)forname,scoresininfo.items():# 遍历键值对print(f{name}\t{scores[chinese]}\t{scores[maths]}\t{scores[english]})3.2.2 查询个人成绩直接通过姓名键获取然后打印各科成绩elifsub2:nameinput(请输入姓名)ifnameininfo:# 成员判断sinfo[name]# 通过键访问值第五期 §5.2print(f姓名{name})print(f语文{s[chinese]})print(f数学{s[maths]})print(f英语{s[english]})else:print(查无此人)3.3 成绩修改对应 第五期 §5.3先展示全部成绩让用户看到现有数据然后输入姓名若存在则选择科目并赋新值。修改嵌套字典中的值时直接通过info[name][key] new_value即可ifselect3:# 展示全部print(姓名\t语文\t数学\t英语)forname,scoresininfo.items():print(f{name}\t{scores[chinese]}\t{scores[maths]}\t{scores[english]})nameinput(请输入要修改的姓名)ifnamenotininfo:print(查无此人)else:print(1语文2数学3英语)sub_idxint(input(请输入修改学科编号))new_scoreint(input(请输入新成绩))# 学科映射subject_map{1:chinese,2:maths,3:english}ifsub_idxinsubject_map:# 修改嵌套字典中的值第五期 §5.3info[name][subject_map[sub_idx]]new_scoreprint(成绩修改成功)else:print(学科编号错误)⚠️ 注意info[name]获取的是内层字典如{chinese:90, maths:88, english:95}然后通过[key]进一步定位到具体科目。这与第五期 §5.3中介绍的字典修改方式一致。3.4 成绩删除对应 第五期 §5.4同样先展示全部然后按姓名删除条目。使用del语句删除字典的键值对第五期 §5.4ifselect4:print(姓名\t语文\t数学\t英语)forname,scoresininfo.items():print(f{name}\t{scores[chinese]}\t{scores[maths]}\t{scores[english]})nameinput(请输入要删除的姓名)ifnameininfo:delinfo[name]# 删除该键值对第五期 §5.4print(删除成功)else:print(查无此人)⚠️ 安全提醒删除不存在的键会抛出KeyError所以一定要先用in检查。这也在第五期 §5.4中有详细说明。3.5 退出系统ifselect5:print(退出系统欢迎再次使用)break四、完整可运行代码将以上所有片段整合加上菜单循环就是一套完整的字典版成绩录入系统。代码已测试通过Python 3.6 均可运行。# 成绩录入系统字典版menu 1成绩录入 2成绩查询 3成绩修改 4成绩删除 5退出系统 info{刘备:{chinese:90,maths:88,english:95},张飞:{chinese:70,maths:75,english:80}}whileTrue:print(menu)selectint(input(请输入管理选项))ifselect1:nameinput(请输入姓名)ifnameininfo:print(姓名已存在请重新录入)else:chint(input(请输入语文成绩))maint(input(请输入数学成绩))enint(input(请输入英语成绩))info[name]{chinese:ch,maths:ma,english:en}print(f{name}成绩录入成功)elifselect2:print(1查询全部成绩2查询个人成绩)subinput(请选择功能项)ifsub1:print(姓名\t语文\t数学\t英语)forname,scoresininfo.items():print(f{name}\t{scores[chinese]}\t{scores[maths]}\t{scores[english]})elifsub2:nameinput(请输入姓名)ifnameininfo:sinfo[name]print(f姓名{name})print(f语文{s[chinese]})print(f数学{s[maths]})print(f英语{s[english]})else:print(查无此人)else:print(选项有误)elifselect3:print(姓名\t语文\t数学\t英语)forname,scoresininfo.items():print(f{name}\t{scores[chinese]}\t{scores[maths]}\t{scores[english]})nameinput(请输入要修改的姓名)ifnamenotininfo:print(查无此人)else:print(1语文2数学3英语)sub_idxint(input(请输入修改学科编号))new_scoreint(input(请输入新成绩))subject_map{1:chinese,2:maths,3:english}ifsub_idxinsubject_map:info[name][subject_map[sub_idx]]new_scoreprint(成绩修改成功)else:print(学科编号错误)elifselect4:print(姓名\t语文\t数学\t英语)forname,scoresininfo.items():print(f{name}\t{scores[chinese]}\t{scores[maths]}\t{scores[english]})nameinput(请输入要删除的姓名)ifnameininfo:delinfo[name]print(删除成功)else:print(查无此人)elifselect5:print(退出系统欢迎再次使用)breakelse:print(无效选项请重新输入)五、列表 vs 字典到底好在哪里结合第五期总结中的核心知识点汇总表我们从多个维度对比两种实现方式对比维度列表实现字典实现说明查找速度O(n) 遍历O(1) 哈希查找数据量越大差距越明显代码可读性依赖数字下标i[1]、i[2]易错语义化键名一目了然可维护性大幅提升扩展性增加字段需修改所有下标增加字段只需在内层字典加键改动范围极小数据完整性无强制字段约束可通过字典键名约束结构减少数据不一致 bug内存占用稍小列表更紧凑稍大哈希表开销对现代计算机可忽略成员判断if name in [row[0] for row in info]if name in info语法更简洁对于这种“增删改查”为主的小系统字典在速度上的优势可能并不明显因为数据量小但可维护性的提升是实打实的。当你后期想给每个学生增加“性别”字段时列表版需要改动所有位置的下标涉及大量代码修改字典版只需在内层字典加一个键即可其余代码完全不受影响六、避坑指南新手常犯的字典错误在改写过程中有几个地方特别容易出错结合第五期 §5和第五期 §8的知识点我在此一一提醒序号错误写法正确写法原因1for i in info: print(i[0])for name, scores in info.items():直接遍历字典拿到的是键姓名对字符串取下标会得到第一个字符2if info.get(name) is not None:if name in info:成绩可能为 0get()会返回None造成误判3info[name] 100info[name][chinese] 100前者会覆盖整个内层字典后者只修改指定科目4del info[name]不检查if name in info: del info[name]删除不存在的键会抛出KeyError5score input(成绩)score int(input(成绩))input()返回字符串可能导致比较或计算错误重点说明错误1涉及字典的遍历方式。在第五期 §5.2中我们强调过直接遍历字典默认遍历的是键而不是键值对。正确的做法是使用items()方法# ❌ 错误foriininfo:print(i)# 输出刘备、张飞姓名print(i[0])# 输出刘、张姓名的第一个字符# ✅ 正确forname,scoresininfo.items():print(name)# 输出刘备、张飞print(scores)# 输出{chinese:90, maths:88, english:95}错误2涉及dict.get()方法的返回值陷阱。在第五期 §5.2中我们提到get()方法在键不存在时返回None。但如果某个学生的某科成绩恰好为 0get()返回0与None不同所以这个判断逻辑本身没问题——但更常见的问题是用get()判断键是否存在时如果值可能是None或0就会产生歧义。因此最安全的方式是直接使用in。七、扩展思考如果允许重名怎么办实际业务中姓名重复很常见。若要支持重名就不能用姓名当主键而应该采用方案 A用学号或自动递增ID作为外层键内层字典包含姓名和成绩。info{1001:{name:刘备,chinese:90,maths:88,english:95},1002:{name:刘备,chinese:70,maths:75,english:80},1003:{name:张飞,chinese:85,maths:90,english:78}}这种设计的查询流程变为# 按姓名查找可能返回多条results[]forsid,dataininfo.items():ifdata[name]name:results.append((sid,data))查询时根据姓名可能会搜出多条记录需要进一步选择。这种设计更接近真实数据库但代码复杂度会稍高一些涉及第五期 §5.2中的字典遍历和条件筛选。动手挑战尝试基于上述数据结构重写查询和删除功能使其支持同名学生的处理。八、知识点对应与总结8.1 本文知识点与笔记章节对照知识点对应笔记具体位置列表的创建与访问第五期 组合数据类型§2.1、§2.2列表的遍历第五期 组合数据类型§2.2字典的创建与访问第五期 组合数据类型§5.1、§5.2字典的添加与修改第五期 组合数据类型§5.3字典的删除第五期 组合数据类型§5.4字典的遍历items()第五期 组合数据类型§5.2成员判断in第二期 基础语法·下篇§5 成员运算符可变与不可变类型第五期 组合数据类型§8 补充知识8.2 核心收获通过这次重构我们不仅将代码从“过程式下标操作”升级为“字典键值对操作”更重要的是养成了一种“数据驱动”的编程思维选择合适的数据结构往往能让代码更简洁、更健壮。字典是 Python 中最常用的数据结构之一熟练掌握它的遍历、增删改查、嵌套操作是写好 Python 程序的必经之路。本文涉及的字典操作在第五期 §5字典中都有系统讲解建议结合笔记对照学习。8.3 下一步学习建议如果对字典的底层原理感兴趣可以深入了解哈希表的工作机制——这是字典 O(1) 查找速度的根源如果想进一步提升数据管理能力可以学习Pandas库中的DataFrame结构它是二维数据处理的终极利器如果想探索更复杂的嵌套结构可以尝试实现“班级-学生-成绩”三级字典并实现按班级统计平均分等功能本文代码已测试通过Python 3.6 均可运行。如果觉得有用点个赞再走吧 — Compiled and Authored by Whisky — July 12th, 2026