要不要拿点东西练手?很多初学者在学完 Python 基础语法之后,都会有这么一段迷茫期:变量、列表、字典、文件操作、函数、类,单拎出来各个都看得懂,可一关掉教程就不知道这些东西到底怎么串起来用。教务系统这种项目,是练手最划算的选择之一。功能明确、逻辑环环相扣,但又不会复杂到让新手劝退。把“学生管理、课程管理、成绩管理、选课”这些传统业务在控制台里跑起来,写完之后你会发现,之前学的那些零散知识点全都有了下落。
这篇文章就把我实际写的一个“简单版教务系统”完整拆开给你看,从需求分析、表结构设计到具体的 Python 代码实现、踩过的坑、排查思路,全部复盘。不搞花哨的图形界面,只做命令行交互,把核心逻辑吃透。整体代码量控制在 300 行到 400 行,无论你是刚入门想找项目练手,还是想看看一个完整系统是怎么组织代码的,都能在这里找到点有用的东西。
1. 系统需求与整体设计思路
1.1 需求拆解:一个“教务系统”到底需要哪些功能
教务系统听起来是个很宏大的概念,但落到“简单版”这个定位上,核心业务就四件事:管学生、管课程、管成绩、管选课。围绕这四件事,最基础的功能需求如下:
- 学生管理:新增学生、查看学生列表、按学号查询、修改学生信息、删除学生。
- 课程管理:新增课程、查看课程列表、按课程号查询、删除课程。
- 选课管理:学生选课、查看某学生已选课程、退选课程。
- 成绩管理:录入学生某门课的成绩、按学号或课程查看成绩单。
- 数据存储:程序重启之后数据不丢失。
一开始很容易犯的毛病是“功能越加越多”,今天想加个教师端,明天想加个排课模块。但既然是练手项目,把上面这几条做扎实了,比堆砌一堆半成品功能有用得多。我最终给这个系统划定了边界:不做GUI、不做Web、不做权限分级,只有管理员一个角色,交互方式就是命令行菜单。
选这个边界是有原因的。控制台交互能让你把注意力集中在数据结构和业务流程上。你一旦上了 Flask 或 Tkinter,注意力就会分散到路由、模板、组件布局上,反而淡化了“训练 Python 核心能力”这个目标。
1.2 数据存储方案选型:为什么不直接写文件
第二个需要做决定的地方是数据怎么存。最简单的选择是把数据写进文本文件或 JSON 文件,高级一点的是用 SQLite,再往上就是 MySQL 之类的数据库服务器。
我的建议是直接用 sqlite3。它是 Python 标准库自带的模块,不需要安装任何第三方包,却能让你写出真正的 SQL 语句,体验完整的数据库操作流程。JSON 文件方案在数据量小的时候确实能跑,但你要自己处理“读文件—改数据—写回文件”的全过程,还要考虑并发写入时数据损坏的问题。SQLite 把这些都封装好了,你只需要用标准 SQL 语句就能完成增删改查。
用 SQLite 还有一个隐藏好处:将来如果你要把这个系统升级成 Web 版,或者把数据迁移到 MySQL,你的 Data Access 层代码基本是通用的,改动成本极低。用 JSON 文件实现的数据层,到了那时几乎要全部重写。
1.3 代码结构规划:别把全部代码堆在一个文件里
初学者很容易把所有代码都堆进一个 main.py,刚开始写的时候挺爽,写到后面就会头疼:函数之间互相调用,变量作用域混乱,找一个 bug 要翻半天。
我在动手之前就把代码规划成了三个模块:
| 模块 | 职责 | 对应文件 |
|---|---|---|
| 数据库层 | 负责建表、连接数据库、执行 SQL 的通用函数 | db.py |
| 业务层 | 学生、课程、选课、成绩的具体操作函数 | models.py |
| 交互层 | 控制台菜单、用户输入、调用业务函数 | main.py |
这样分层之后,每一层的代码都只关注自己的事情。比如你想调整菜单交互逻辑,只需要改 main.py;想让课程号自动生成而不用手输,只需要改 models.py 里的相应函数。“低耦合,高内聚”不是喊口号,这种小项目里照做,收益立竿见影。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库表结构设计与初始化
2.1 四张表的关系模型
在设计数据库表之前,先把业务实体梳理清楚:学生、课程、成绩、管理员账号。其中成绩表并不是独立存在的,它是“学生”和“课程”之间的关联表,专门用来存“某学生选了某门课”以及“这门课考了多少分”。
表结构如下:
-
students 表
id:主键,自增(其实学号本身可以做主键,但用自增 id 更符合数据库规范)student_id:学号,设置 UNIQUE 约束name:姓名major:专业
-
courses 表
id:主键,自增course_id:课程号,UNIQUE 约束course_name:课程名credit:学分teacher:授课教师
-
scores 表
id:主键,自增student_id:学号(外键指向 students.student_id)course_id:课程号(外键指向 courses.course_id)score:成绩,默认 NULL 表示还没考试
-
users 表
id:主键,自增username:用户名password:密码
为什么学号、课程号不用外键约束?在 SQLite 里,外键约束默认是关闭的,而且成绩表里的 student_id 和 course_id 如果加了真实的外键约束,删除学生或课程时会遇到外键冲突,处理起来会麻烦一点。这个项目作为练手,我选择在业务逻辑层手动维护一致性,也就是删除学生时“手动”把成绩表里对应的记录一起删掉。真实项目里,用外键加上 ON DELETE CASCADE 是更稳妥的方案,这一点你可以作为扩展方向自己去验证。
2.2 数据库初始化代码
我用一个独立的 db.py 处理数据库的创建和连接。这个文件里只放数据库层的逻辑,不掺任何业务判断。
python复制import sqlite3
DB_NAME = "edu_system.db"
def get_connection():
conn = sqlite3.connect(DB_NAME)
conn.row_factory = sqlite3.Row
return conn
def init_db():
conn = get_connection()
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
major TEXT DEFAULT ''
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS courses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
course_id TEXT UNIQ
