实验专题(一):从列表到字典——手把手重构你的成绩录入系统

📅 2026/7/12 21:08:23 👁️ 阅读次数 📝 编程学习
实验专题(一):从列表到字典——手把手重构你的成绩录入系统

实验专题(一):从列表到字典——手把手重构你的成绩录入系统

当你的数据需要快速按“姓名”查找时,字典是比列表更优雅的数据结构。本文基于《Python 学习笔记(第五期)——组合数据类型》中关于列表和字典的核心知识,带你一步步将基于列表实现的成绩管理系统重构为基于字典的实现,并详细解释每一步的设计思路与坑点。

原实验:Python实战:手把手带你撸一个成绩管理系统(附源码+详细注释)

关联笔记

  • Python 学习笔记(第二期)——基础语法(下篇):运算符全解与实战演练 —— §5 成员运算符(in/not in
  • Python 学习笔记(第五期)——组合数据类型:列表、元组、集合与字典精讲 —— §2 列表、§5 字典、§8 可变与不可变类型

更多Python知识:Python基础入门

我的主页:AOwhisky,这里有更多运维系统性知识整理和其他有趣内容,欢迎与我一起探讨学习~

📑 实验目录

  1. 缘起:一个经典的小项目
  2. 目标:用字典重构数据结构
  3. 功能模块逐一改写
  4. 完整可运行代码
  5. 列表 vs 字典:对比总结
  6. 避坑指南:新手常犯的字典错误
  7. 扩展思考:如果允许重名怎么办?
  8. 知识点对应与总结

一、缘起:一个经典的小项目

很多 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.3):

info={'刘备':{'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字典元素的添加)。

ifselect==1:name=input('请输入姓名:')ifnameininfo:# 成员运算符 in,时间复杂度 O(1)print('姓名已存在,请重新录入!')else:ch=int(input('请输入语文成绩:'))ma=int(input('请输入数学成绩:'))en=int(input('请输入英语成绩:'))# 字典添加新键值对(第五期 §5.3)info[name]={'chinese':ch,'maths':ma,'english':en}print(f'{name}成绩录入成功!')

核心优势in操作在字典中平均时间复杂度为O(1),远快于列表的O(n)。随着数据量增长,这个差距会越来越明显。

3.2 成绩查询(对应 第五期 §5.2)

3.2.1 查询全部成绩

遍历字典的items()方法(第五期 §5.2),同时拿到姓名和成绩字典:

ifsub=='1':print('姓名\t语文\t数学\t英语')forname,scoresininfo.items():# 遍历键值对print(f'{name}\t{scores["chinese"]}\t{scores["maths"]}\t{scores["english"]}')
3.2.2 查询个人成绩

直接通过姓名键获取,然后打印各科成绩:

elifsub=='2':name=input('请输入姓名:')ifnameininfo:# 成员判断s=info[name]# 通过键访问值(第五期 §5.2)print(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即可:

ifselect==3:# 展示全部print('姓名\t语文\t数学\t英语')forname,scoresininfo.items():print(f'{name}\t{scores["chinese"]}\t{scores["maths"]}\t{scores["english"]}')name=input('请输入要修改的姓名:')ifnamenotininfo:print('查无此人!')else:print('1:语文;2:数学;3:英语')sub_idx=int(input('请输入修改学科编号:'))new_score=int(input('请输入新成绩:'))# 学科映射subject_map={1:'chinese',2:'maths',3:'english'}ifsub_idxinsubject_map:# 修改嵌套字典中的值(第五期 §5.3)info[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.4):

ifselect==4:print('姓名\t语文\t数学\t英语')forname,scoresininfo.items():print(f'{name}\t{scores["chinese"]}\t{scores["maths"]}\t{scores["english"]}')name=input('请输入要删除的姓名:')ifnameininfo:delinfo[name]# 删除该键值对(第五期 §5.4)print('删除成功!')else:print('查无此人!')

⚠️ 安全提醒:删除不存在的键会抛出KeyError,所以一定要先用in检查。这也在第五期 §5.4中有详细说明。

3.5 退出系统

ifselect==5: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)select=int(input('请输入管理选项:'))ifselect==1:name=input('请输入姓名:')ifnameininfo:print('姓名已存在,请重新录入!')else:ch=int(input('请输入语文成绩:'))ma=int(input('请输入数学成绩:'))en=int(input('请输入英语成绩:'))info[name]={'chinese':ch,'maths':ma,'english':en}print(f'{name}成绩录入成功!')elifselect==2:print('1:查询全部成绩;2:查询个人成绩')sub=input('请选择功能项:')ifsub=='1':print('姓名\t语文\t数学\t英语')forname,scoresininfo.items():print(f'{name}\t{scores["chinese"]}\t{scores["maths"]}\t{scores["english"]}')elifsub=='2':name=input('请输入姓名:')ifnameininfo:s=info[name]print(f'姓名:{name}')print(f'语文:{s["chinese"]}')print(f'数学:{s["maths"]}')print(f'英语:{s["english"]}')else:print('查无此人!')else:print('选项有误!')elifselect==3:print('姓名\t语文\t数学\t英语')forname,scoresininfo.items():print(f'{name}\t{scores["chinese"]}\t{scores["maths"]}\t{scores["english"]}')name=input('请输入要修改的姓名:')ifnamenotininfo:print('查无此人!')else:print('1:语文;2:数学;3:英语')sub_idx=int(input('请输入修改学科编号:'))new_score=int(input('请输入新成绩:'))subject_map={1:'chinese',2:'maths',3:'english'}ifsub_idxinsubject_map:info[name][subject_map[sub_idx]]=new_scoreprint('成绩修改成功!')else:print('学科编号错误!')elifselect==4:print('姓名\t语文\t数学\t英语')forname,scoresininfo.items():print(f'{name}\t{scores["chinese"]}\t{scores["maths"]}\t{scores["english"]}')name=input('请输入要删除的姓名:')ifnameininfo:delinfo[name]print('删除成功!')else:print('查无此人!')elifselect==5: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:成绩可能为 0,get()会返回None造成误判
3info[name] = 100info[name]['chinese'] = 100前者会覆盖整个内层字典,后者只修改指定科目
4del info[name]不检查if name in info: del info[name]删除不存在的键会抛出KeyError
5score = 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。但如果某个学生的某科成绩恰好为 0,get()返回0,与None不同,所以这个判断逻辑本身没问题——但更常见的问题是用get()判断键是否存在时,如果值可能是None0,就会产生歧义。因此最安全的方式是直接使用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