这个项目是一个命令行版本的学生管理系统,主要实现:
新增学生 显示学生 查找学生 删除学生 存档到文件整个程序虽然不算复杂,但是里面已经包含了一个完整小程序应该有的基本结构。
一、先确定学生管理系统要做什么
程序启动之后,先显示一个菜单,让用户选择操作。
菜单可以设计成:
1. 新增学生信息 2. 显示所有同学信息 3. 根据名字查找学生信息 4. 删除学生信息 0. 退出程序然后不断读取用户输入,根据不同的数字进入不同的功能。
这种程序很适合使用:
whileTrue:让菜单一直运行。
如果用户输入0,直接退出程序。
二、学生数据怎么保存?
这是写这个项目时我觉得最关键的一步。
一个学生肯定不只是一个数据。
比如一个学生至少有:
学号 姓名 性别 班级如果给每个学生都创建四个独立变量:
studentId=...name=...gender=...className=...学生一多就很难管理。
所以这里可以使用字典表示一个学生。
例如:
student={'studentId':studentId,'name':name,'gender':gender,'className':className}这样,一个字典就表示一个完整的学生信息。
三、多个学生怎么办?
一个学生用字典保存,那多个学生就可以使用列表。
程序中准备一个全局列表:
students=[]每增加一个学生,就把对应的字典放进去:
students.append(student)最终的数据结构其实就是:
students ↓ [ student1, student2, student3, ... ]其中每一个student又是一个字典。
也就是:
列表 ↓ 多个字典 ↓ 每个字典保存一个学生的信息这种设计其实非常自然,也把前面学到的list和dict结合起来了。
四、创建程序入口
程序可以设计一个main()作为入口函数。
整体逻辑大概是:
defmain():whileTrue:choice=menu()ifchoice==0:sys.exit()elifchoice==1:insert()elifchoice==2:show()elifchoice==3:find()elifchoice==4:delete()else:print("您的输入有误! 请重新输入!")最后再调用:
main()这样整个程序的组织结构就比较清楚了。
main()不负责具体实现每一个功能,而是负责“指挥”。
真正的功能分别交给:
menu() insert() show() find() delete()去完成。
五、菜单函数
菜单本身单独封装成一个函数:
defmenu():print(' 1. 新增学生')print(' 2. 显示学生')print(' 3. 查找学生')print(' 4. 删除学生')print(' 0. 退出程序')choice=input(' 请输入您的选择: ')returnchoice把用户的输入交给main()判断。
六、新增学生
新增学生的流程比较简单:
输入学号 ↓ 输入姓名 ↓ 输入性别 ↓ 输入班级 ↓ 检查数据 ↓ 创建字典 ↓ 加入 students首先输入基本信息:
studentId=input("请输入学生的学号: ")name=input("请输入学生的姓名: ")gender=input("请输入学生的性别: ")className=input("请输入学生的班级: ")性别这里还进行了简单的检查:
ifgendernotin('男','女'):print('性别输入的内容不符合要求, 新增失败!')return如果输入不符合要求,就直接结束这次新增操作。
之后创建学生字典:
student={'studentId':studentId,'name':name,'gender':gender,'className':className}最后:
students.append(student)把学生放进列表。
七、显示所有学生
有了:
students之后,显示全部学生就很简单了。
直接遍历:
forsinstudents:然后通过字典的 key 获取信息:
s['studentId']s['name']s['gender']s['className']例如可以按照:
学号 姓名 性别 班级这样的格式打印。
最后还可以使用:
len(students)统计当前一共有多少条学生记录。
这个功能实际上就是:
遍历列表 ↓ 访问字典 ↓ 输出数据八、根据姓名查找学生
查找功能需要先让用户输入一个名字:
name=input("请输入要查找的同学姓名: ")然后遍历所有学生:
forsinstudents:检查:
ifname==s['name']:如果匹配,就把学生信息打印出来。
同时使用:
count+=1记录一共找到了多少人。
这里有一个实际开发中的小特点:
姓名不一定唯一。
所以这里并没有找到第一个就结束,而是把整个列表遍历完,把所有同名学生都找出来。
九、删除学生
删除学生时采用学号进行查找。
流程:
输入学号 ↓ 遍历 students ↓ 找到对应学生 ↓ 从列表中删除核心操作:
ifstudentId==s['studentId']:students.remove(s)这里使用学号而不是姓名,也是因为学号更加适合作为学生的唯一标识。
十、为什么需要存档?
到目前为止,程序虽然可以完成:
新增 显示 查找 删除但是还有一个很明显的问题:
程序一旦退出,之前的数据就没了。
因为现在的:
students=[]本质上只是保存在程序运行期间的内存里。
所以需要加入“存档”功能,把学生信息保存到文件中。
这里约定使用:
record.txt来保存学生数据。
十一、设计存档文件的格式
这里没有使用复杂的数据库,而是直接使用文本文件。
规定:
一个学生占一行 学生的不同信息之间使用 \t 分隔例如:
学号 名字 性别 班级这里的分隔符实际上是:
\t也就是制表符。
这种格式非常简单,但是对于这个小项目已经够用了。
十二、实现保存函数
保存函数可以写成:
defsave():withopen('record.txt','w',encoding='utf-8')asf:forsinstudents:f.write(f"{s['studentId']}\t{s['name']}\t{s['gender']}\t{s['className']}\n")print(f'[存档成功] 共存储了{len(students)}条记录!')核心逻辑就是:
打开文件 ↓ 遍历 students ↓ 把每个学生写成一行 ↓ 关闭文件这里使用的是:
withopen(...)所以不需要手动写close(),代码块结束之后文件会自动关闭。
十三、什么时候保存数据?
在学生信息发生变化的时候,就应该重新存档。
也就是:
新增学生 ↓ save() 删除学生 ↓ save()这样程序的内存数据和文件中的数据就能保持更新。
十四、重新启动程序,还需要把数据读回来
光有save()还不够。
程序重新启动之后:
students=[]还是一个空列表。
所以启动的时候,需要调用load(),把文件中的数据重新恢复到students中。
十五、读取存档文件
读取数据时先判断文件存不存在:
ifnotos.path.exists('record.txt'):return如果文件不存在,就跳过读档。
如果存在,就:
students=[]先准备一个空的学生列表。
然后打开文件:
withopen('record.txt','r',encoding='utf-8')asf:接下来逐行读取:
forlineinf:十六、把一行数据重新拆成学生信息
存档的时候,我们把数据用:
\t隔开。
读档时就反过来,把这一行重新拆开:
line=line.strip()tokens=line.split('\t')如果拆出来的数据数量不足:
iflen(tokens)!=4:print(f'当前行格式存在问题! line={line}')continue就说明这行数据格式有问题,直接跳过。
正常情况下:
tokens[0] → 学号 tokens[1] → 姓名 tokens[2] → 性别 tokens[3] → 班级然后重新构造字典:
student={'studentId':tokens[0],'name':tokens[1],'gender':tokens[2],'className':tokens[3]}再:
students.append(student)这样文件中的学生信息就重新恢复到内存中了。
十七、整个数据流程
把存档和读档放在一起看,整个过程其实非常清晰。
程序运行:
record.txt ↓ load() ↓ students ↓ 增 / 删 / 查 / 显示 ↓ save() ↓ record.txt也就是说:
文件 ↔ 程序内存之间形成了一个简单的数据同步过程。
十八、为什么这里使用“列表 + 字典”?
这个数据结构其实非常适合这个项目。
整体:
students=[]学生列表里面保存:
student={'studentId':...,'name':...,'gender':...,'className':...}所以整体结构就是:
students │ ├── student │ ├── studentId │ ├── name │ ├── gender │ └── className │ ├── student │ ├── studentId │ ├── name │ ├── gender │ └── className │ └── ...列表解决“多个学生”的问题。
字典解决“一个学生有多个属性”的问题。
两者组合起来,刚好满足需求。
十九、完整代码+把程序打包成exe
# 实现一个命令行版本的学生管理系统importsysimportos.path# 使用这个全局变量, 来管理所有的学生信息# 这个列表的每个元素都是一个 "字典", 每个字典就分别表示了一个同学students=[]defsave():""" 用于存档 """withopen('record.txt','w',encoding='utf-8')asf:forsinstudents:f.write(f"{s['studentId']}\t{s['name']}\t{s['gender']}\t{s['className']}\n")print(f'[存档成功] 共存储了{len(students)}条记录!')defload():""" 用于读档 """# 如果存档文件不存在, 则直接跳过读档流程# 为了避免读方式打开文件的时候, 文件不存在引起异常ifnotos.path.exists('record.txt'):returnglobalstudents students=[]withopen('record.txt','r',encoding='utf-8')asf:forlineinf:line=line.strip()tokens=line.split('\t')iflen(tokens)!=4:print(f'当前行格式存在问题! line={line}')continuestudent={'studentId':tokens[0],'name':tokens[1],'gender':tokens[2],'className':tokens[3]}students.append(student)print(f'[读档成功] 共读取了{len(students)}条记录')defmenu():print(' 1. 新增学生')print(' 2. 显示学生')print(' 3. 查找学生')print(' 4. 删除学生')print(' 0. 退出程序')choice=input(' 请输入您的选择: ')returnchoicedefinsert():print('[新增学生] 开始!')studentId=input('请输入学生的学号: ')name=input('请输入学生的姓名: ')gender=input('请输入学生的性别: ')ifgendernotin('男','女'):print('性别输入的内容不符合要求, 新增失败!')returnclassName=input('请输入学生的班级: ')student={'studentId':studentId,'name':name,'gender':gender,'className':className}globalstudents students.append(student)save()print('[新增学生] 完毕!')defshow():print('[显示学生] 开始!')forsinstudents:print(f"[{s['studentId']}]\t{s['name']}\t{s['gender']}\t{s['className']}")print(f'[显示学生] 完毕! 共显示了{len(students)}条数据!')deffind():# 根据学生姓名, 来进行查找print('[查找学生] 开始!')name=input('请输入要查找的同学姓名: ')count=0forsinstudents:ifname==s['name']:print(f"[{s['studentId']}]\t{s['name']}\t{s['gender']}\t{s['className']}")count+=1print(f'[查找学生] 结束! 共找到了{count}个匹配的同学!')defdelete():print('[删除学生] 开始!')studentId=input('请输入要删除的学生学号: ')forsinstudents:ifstudentId==s['studentId']:print(f"删除{s['name']}同学的信息!")students.remove(s)save()print('[删除学生] 结束!')defmain():""" 入口函数 """# 通过控制台和用户进行交互print('----------------------------------------------')print(' 欢迎来到学生管理系统 ')print('----------------------------------------------')load()whileTrue:# 通过 menu 函数来打印出菜单项choice=menu()ifchoice=='1':# 新增学生insert()elifchoice=='2':# 显示学生show()elifchoice=='3':# 查找学生find()elifchoice=='4':# 删除学生delete()elifchoice=='0':# 退出程序print('goodbye!')sys.exit(0)else:print('您的输入有误! 请重新输入!')continuemain()目前程序是一个:
.py文件。
这种文件需要目标机器安装 Python 环境才能运行。
为了方便部署,可以使用:
PyInstaller把 Python 程序打包成.exe。
首先安装:
pipinstallpyinstaller然后进行打包。
其中-F表示打包成单个 exe。
打包完成之后,就可以把生成的程序复制到其他机器上使用,而不需要再单独安装 Python 环境。
二十、这个项目把哪些知识串起来了?
回头看一下这个学生管理系统,里面其实已经用到了不少 Python 基础知识。
函数 ↓ main / menu / insert / show / find / delete 列表 ↓ students 字典 ↓ student 条件语句 ↓ 菜单判断、数据合法性判断 循环 ↓ 菜单循环、遍历学生 文件 ↓ save / load 字符串 ↓ \t 分隔、split、strip 模块 ↓ sys、os 程序部署 ↓ PyInstaller所以这个项目虽然只是一个命令行学生管理系统,但是已经不是单独练习某一个语法了,而是开始尝试把多个知识点组合到一个完整程序中。
二十一、总结
这个学生管理系统最核心的地方,我觉得不是“增删查”本身,而是慢慢理解一个程序应该怎么组织。
比如:
main()负责整个程序的流程。
menu()负责菜单。
insert() show() find() delete()分别负责具体的数据操作。
save() load()负责数据持久化。
这样每个函数各干各的事情,整个程序看起来就不会乱。项目的基本结构也正是围绕这些函数展开的。
如果只是在main()里面把所有代码全部写完,刚开始可能还能看懂,但功能一多,很快就会变得非常混乱。
所以我觉得这个项目真正值得记录的一点就是:
当程序开始变复杂之后,不能只想着“把功能写出来”,还要开始考虑怎么把代码组织起来。
从一个简单的.py文件,到能够完成增删查、保存数据、重新加载数据,最后再打包成 exe,这基本就是一次比较完整的 Python 小项目实践。