ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python课程设计:学生管理系统从SQLite设计到PyInstaller打包exe全流程

Python课程设计:学生管理系统从SQLite设计到PyInstaller打包exe全流程 简介一份完整的Python课程设计项目——学生管理系统面向Python初学者、高校在校生及需要完成实训作业的开发者。系统覆盖了学生信息管理中最核心的录入、查找、删除、修改、排序、统计人数、显示全部信息和退出等八大功能能够帮助读者快速理解命令行交互程序的设计思路与代码组织方式。资源压缩包约5.96MB主要包含三类文件Python源码.py、可直接双击运行的Windows可执行程序.exe以及详细的使用说明文档即使本地没有搭建Python环境也能直接体验系统效果。目前已有767人学习下载学习者可同时获得可运行的完整代码、免配置的演示程序和清晰的文档说明便于对照源码理解每个功能模块的实现逻辑。也可在此基础上扩展文件保存、图形界面等高级功能适合作为课程设计、期末项目或入门练手素材。1. 为什么这个 Python 课程设计要把源码、exe 和使用说明一起交付一份学生管理系统的课程设计做完了交上去之前老师问的往往不是「功能全不全」而是「教室里这台电脑没有 Python我双击哪个文件能跑」。这个标题里的三样交付物正好回答这个问题源码证明逻辑是你写的可执行程序 exe 证明它能脱离开发环境直接运行使用说明证明别人不需要你站在旁边演示。三者缺一评审环节都可能卡住。真正值得认真对待的不只是增删改查那几十行代码而是从源代码到交付物的整条链路表结构怎么定、业务代码怎么分层、PyInstaller 怎么打包、说明文档怎么组织。下面这套流程面向正在做 Python 课程设计的学生也适合想把第一个项目整理成「能交付的工程」的开发者。它不依赖特定版本的 Python用标准库 tkinter 加 SQLite 就能走通最后打包成单个 exe 文件。2. 学生管理系统先设计好 3 张核心表再写代码表结构与 SQLite 选型2.1 学生-课程-成绩用三张表表达课设里最常见的关联关系如果学生管理系统只做一张「学生名单表」答辩时数据库设计基本没有可讲的内容。常见做法是把范围扩展到三个实体学生维护基本信息课程维护开课信息成绩把学生和课程关联起来。这样既覆盖了增删改查又能展示多表查询、LEFT JOIN 和聚合统计正好命中课程设计的能力要求。建表语句直接可以用 SQLite 方言写-- students 学生表学生基本档案 CREATE TABLE IF NOT EXISTS students ( student_id TEXT PRIMARY KEY, -- 学号业务上不允许重复 name TEXT NOT NULL, -- 姓名不允许为空 gender TEXT NOT NULL DEFAULT 男, class_name TEXT NOT NULL DEFAULT , -- 班级 phone TEXT, created_at TEXT DEFAULT (datetime(now, localtime)) ); -- courses 课程表与学生的基本信息分开维护 CREATE TABLE IF NOT EXISTS courses ( course_id INTEGER PRIMARY KEY AUTOINCREMENT, course_name TEXT NOT NULL UNIQUE, credit REAL NOT NULL DEFAULT 2.0 CHECK (credit 0) ); -- scores 成绩表学生与课程的多对多关系落在这里 CREATE TABLE IF NOT EXISTS scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT NOT NULL REFERENCES students(student_id) ON DELETE CASCADE, course_id INTEGER NOT NULL REFERENCES courses(course_id) ON DELETE CASCADE, score REAL NOT NULL DEFAULT 0 CHECK (score 0 AND score 100), UNIQUE (student_id, course_id) ); CREATE INDEX IF NOT EXISTS idx_scores_student ON scores(student_id);表设计里有几点值得在答辩时专门讲student_id 用 TEXT 主键而不是自增主键因为学号本身有业务含义直接用学号做主键查询成绩时少一次 JOIN重复学号也会被数据库直接拦截。course_id 用 INTEGER 自增因为课程没有天然简短编码自增主键录入最省事。scores 表两条外键都带ON DELETE CASCADE删除学生或课程时关联成绩自动清理不会留下孤儿数据。最后的UNIQUE (student_id, course_id)保证同一学生同一课程只能有一条成绩记录。2.2 为什么课设选 SQLite 而不是 MySQL除零配置外还要看交付场景MySQL 写进简历更好看但从课程设计的交付场景看SQLite 有更现实的三个理由数据库是一个单一.db文件复制文件就是备份评审老师的机器不需要安装 MySQL 服务和客户端Python 标准库自带 sqlite3 模块PyInstaller 打包时不会遇到 libmysql.dll 这类依赖问题。要和 MySQL 对比也讲得清楚SQLite 不适合高并发写入不适合多进程同时写同一个库。学生管理系统是单机程序、单人操作、数据量最多几千行正好落在 SQLite 的能力边界内。如果老师追问「团队版或者 Web 版怎么办」可以补一句业务层已经用函数封装 SQL未来把db.py里的连接改成连接池再替换 SQL 方言就能迁到 MySQL。2.3 字段约束让脏数据在进数据库之前就被拒绝建表 SQL 里已经用了 PRIMARY KEY、NOT NULL、UNIQUE、CHECK 四类约束每一条都对应一个真实录入场景。PRIMARY KEY 保证学号唯一NOT NULL 避免「不知道名字的学生」这种无意义记录UNIQUE 保证课程名不重复、同一学生同一课程不重复CHECK 限制学分必须为正、成绩必须在 0 到 100 之间。界面校验要给用户即时提示数据库约束才是数据防线。如果绕过界面直接向数据库插入 150 分SQLite 会抛 IntegrityError程序再把这个异常转成友好提示实现方式在下一章给出。约束和异常提示的对应关系可以先按这个表梳理约束违反时的异常界面应给出的提示PRIMARY KEY(student_id)IntegrityError学号已存在请勿重复录入NOT NULL(name)IntegrityError姓名不能为空UNIQUE(course_name)IntegrityError课程名已存在CHECK(score 0~100)IntegrityError成绩必须在 0~100 之间3. 学生管理系统的源码按三层职责拆数据库层、业务层与界面层的写法3.1 三层拆分界面、业务、数据库访问放不同文件课程设计源码最常见的毛病是一个 main.py 写到底按钮事件回调里直接写 SQL窗口逻辑和数据操作混在一起答辩问「这个模块干什么」答不上来。较稳妥的源码结构是三层职责分离目录可以这样组织student_management/ ├─ main.py # 入口初始化数据库并启动窗口 ├─ db.py # 数据库连接、建表、初始化演示数据 ├─ models.py # 业务函数增删改查和校验 ├─ ui.py # 界面层tkinter 窗口和事件绑定 └─ requirements.txt # 依赖清单记录 pyinstaller 版本db.py 只负责「拿到连接、建表」不写任何界面逻辑models.py 封装 add_student、delete_student、search_students、add_score 这类业务函数ui.py 只做事件转发把用户输入组装成参数传给 models。这样做的好处是改界面不碰 SQL换数据库不碰界面答辩时能清楚讲出每个文件的职责。3.2 数据库访问层连接、建表、初始化默认数据的 db.pydb.py 是整个程序的数据入口先解决两个基础问题外键默认不开启以及首次运行时需要有一批演示数据让界面不空。# db.py import sqlite3 import os DB_PATH os.path.join(os.path.dirname(os.path.abspath(__file__)), student.db) def get_connection(): 获取连接并启用外键约束。SQLite 默认不启用外键。 conn sqlite3.connect(DB_PATH) conn.execute(PRAGMA foreign_keys ON) conn.row_factory sqlite3.Row # 查询结果支持按字段名访问 return conn def init_db(): 建表并写入少量演示数据便于第一次启动时看到界面效果。 from contextlib import closing with closing(get_connection()) as conn: with conn: # 事务上下文出错自动回滚 conn.executescript(此处粘贴第 2.1 节的建表 SQL) count conn.execute(SELECT COUNT(*) AS c FROM students).fetchone()[c] if count 0: conn.execute( INSERT INTO students(student_id, name, gender, class_name) VALUES (?,?,?,?), (2025001, 张同学, 男, 计科1班), )PRAGMA foreign_keys ON必须放在 get_connection 里因为 SQLite 每次新建连接都要重新执行这条 PRAGMA不开启的话 ON DELETE CASCADE 不会生效。with conn:是 sqlite3 的事务写法块内所有语句成功才提交任一条失败自动回滚避免建了一半天发现表不完整。用closing包住连接是因为 sqlite3 的连接对象不推荐直接作为上下文管理器使用。3.3 学生增删改查的代码怎么写参数化查询与事务回滚models.py 是业务核心所有 SQL 都走参数化占位符不拼接字符串。这里给出学生管理和成绩录入的完整实现# models.py import sqlite3 from db import get_connection def add_student(student_id, name, gender, class_name, phone): 新增学生。学号重复、姓名为空由数据库约束拦截后转成业务提示。 try: with get_connection() as conn: conn.execute( INSERT INTO students(student_id, name, gender, class_name, phone) VALUES (?,?,?,?,?), (student_id.strip(), name.strip(), gender, class_name.strip(), phone.strip()), ) except sqlite3.IntegrityError as exc: if UNIQUE in str(exc) or PRIMARY in str(exc): raise ValueError(学号已存在请检查输入) raise ValueError(f数据不合法{exc}) def add_score(student_id, course_name, score): 给指定学生录入成绩。课程不存在时自动创建。 try: with get_connection() as conn: row conn.execute( SELECT course_id FROM courses WHERE course_name?, (course_name,) ).fetchone() if row is None: cur conn.execute( INSERT INTO courses(course_name) VALUES(?), (course_name,) ) course_id cur.lastrowid else: course_id row[course_id] conn.execute( INSERT INTO scores(student_id, course_id, score) VALUES(?,?,?), (student_id, course_id, float(score)), ) except sqlite3.IntegrityError as exc: raise ValueError(f成绩录入失败{exc}) def query_students(keyword): 按学号或姓名模糊查询keyword 为空时返回全部学生及平均分。 with get_connection() as conn: if keyword: rows conn.execute( SELECT s.student_id, s.name, s.class_name, ROUND(AVG(sc.score), 1) AS avg_score FROM students s LEFT JOIN scores sc ON sc.student_id s.student_id WHERE s.student_id LIKE ? OR s.name LIKE ? GROUP BY s.student_id, (f%{keyword}%, f%{keyword}%), ).fetchall() else: rows conn.execute( SELECT s.student_id, s.name, s.class_name, ROUND(AVG(sc.score), 1) AS avg_score FROM students s LEFT JOIN scores sc ON sc.student_id s.student_id GROUP BY s.student_id ).fetchall() return [dict(row) for row in rows] def delete_student(student_id): 删除学生。成绩表有 ON DELETE CASCADE关联成绩自动清理。 with get_connection() as conn: cur conn.execute( DELETE FROM students WHERE student_id?, (student_id,) ) if cur.rowcount 0: raise ValueError(该学号不存在无法删除)add_score 先查课程再决定插入还是复用这样用户在成绩录入页输课程名就行不需要提前去课程页维护课程。query_students 用 LEFT JOIN 保证没有成绩的学生也会出现在列表里AVG 聚合算平均分配合 ROUND 保留一位小数。delete_student 里检查 rowcount为 0 说明学号不存在避免静默失败。所有函数都把 sqlite3.IntegrityError 转换成 ValueError界面层只捕获 ValueError 这一个异常类型向用户弹提示。3.4 答辩时最常被追问的 4 个边界查不到、重复、越界、输入非法这 4 个边界用一张表说清再补一段校验代码场景不做边界处理的现象正确做法输入 150 分的成绩数据写进表统计全部失真CHECK 约束拦截界面捕获后提示连续两次点「新增」第二份数据被主键约束拒绝捕获 IntegrityError 转成业务提示删除不存在的学号静默成功界面无反馈用 cursor.rowcount 判断为 0 时报错电话号码里混入字母存进库后无法联系界面层用正则做一次预校验import re if phone and not re.fullmatch(r\d{3}-?\d{8}, phone): messagebox.showerror(提示, 电话格式不正确示例010-12345678)提示界面校验负责体验数据库约束负责兜底两层都不该省。答辩时能说清这个设计比多写两个按钮更加分。4. 用 PyInstaller 把 Python 学生管理系统的源码打包成独立 exe4.1 打包前把环境固定下来打包前先建一个干净的虚拟环境避免把开发环境里无关的第三方包一起收进 exepython -m venv pyinstaller_env pyinstaller_env\Scripts\activate pip install pyinstaller这个项目只用 tkinter 和标准库 sqlite3虚拟环境里只需要 pyinstaller 一个包requirements.txt 里写清版本即可。先用干净环境打包还有一层好处如果之后程序里加了 requests 或 pandas 这类第三方库PyInstaller 不一定能自动收集全部动态导入的子模块到时候问题更容易定位在依赖收集上而不是业务代码本身。4.2 pyinstaller 打包命令的 6 个参数-F、-w、-n、--icon、--clean、--hidden-importpyinstaller -F -w -n StudentManagement --iconapp.ico --clean --hidden-importtkinter main.py执行完在 dist 目录下得到 StudentManagement.exe这就是交付物。各参数的作用和去留场景如下参数作用什么时候可以去掉或调整-F打包成单个 exe 文件调试阶段可换 -D 目录模式启动更快-w运行时不显示黑色控制台窗口需要看 print 输出时去掉-n指定生成 exe 文件名称不指定时默认叫 main--icon设置 exe 图标没有 .ico 文件时省略--clean清理 PyInstaller 缓存再打包反复打包出问题时必加--hidden-import强制收集未被静态分析的模块库使用字符串导入时用到打包后建议保留 StudentManagement.spec 文件它是 PyInstaller 生成的构建配置。下次调整参数直接改 spec 再执行pyinstaller StudentManagement.spec比把一长串命令行存进文档更可靠。--hidden-importtkinter在这种标准库场景一般不会真正触发但加上无害如果将来在 models 里 import pymysql 这类第三方库且库内部用字符串动态导入子模块就需要给每个漏掉的模块单独加这个参数。4.3 exe 运行时报路径错误的修复sys._MEIPASS 与数据库文件的存放位置PyInstaller 的 -F 单文件模式运行时会先把自身解压到系统临时目录__file__指向临时路径。db.py 如果继续按源码方式用__file__定位数据库会出现两个现象每次启动都连同一个临时目录里的空库程序一退出临时目录被清空用户录的 data 全部消失。这是学生管理系统打包后最常见的问题。常见做法是用 PyInstaller 注入的 frozen 标记区分两种运行状态import sys, os def app_dir() - str: 源码运行时返回项目目录exe 运行时返回 exe 所在目录。 if getattr(sys, frozen, False): return os.path.dirname(sys.executable) return os.path.dirname(os.path.abspath(__file__)) DB_PATH os.path.join(app_dir(), student.db)sys.frozen 是 PyInstaller 运行时注入的属性用它判断当前是 exe 还是源码不需要自行猜测。数据库文件放在 exe 同目录最直观用户能看到 StudentManagement.exe 和 student.db 成对出现删除 db 文件即可恢复初始状态。如果 exe 被放到 Program Files 这类不可写目录需要在写入失败时回退到%APPDATA%路径这个兜底逻辑可以作为加分点写进代码注释。4.4 杀软误报与源码保护PyInstaller 不负责这两件事打包产物经常遇到「Windows Defender 报毒」的求助帖。原因是 PyInstaller 单文件模式自带自解压特征部分杀软会对这种 Heuristic 行为报风险并非程序真的感染了病毒。处理方式按优先级排序提交误报申诉、给 exe 设置正式的版本信息和图标、或改用 -D 目录模式交付目录模式误报率通常低很多。至于「exe 能不能转回 py」这个问题要直接说清边界PyInstaller 的 pyz 归档不是加密容器用工具可以抽出字节码但还原后的代码可读性差Python 3.11 以上还需要配套反编译器。如果课程设计明确要求保护源码应该用 PyArmor 做混淆而不是指望 PyInstaller。PyInstaller 解决的是「能不能跑起来」不解决「源码防还原」。5. 交付前写好使用说明再给 exe 加一个 --selfcheck 自检参数5.1 使用说明的目录按「拿到压缩包的人」的顺序写不抄网上模板使用说明不是程序文档的搬运它要回答三件事怎么打开、操作顺序是什么、出问题找谁。一个适合课设交付的使用说明结构如下章节内容对应证据运行环境Windows 10/11无需安装 Python可执行程序 exe启动方式双击 StudentManagement.exe源码运行用python main.py无功能操作新增学生、成绩录入、查询、删除的完整点击路径每个功能一张截图数据说明student.db 在 exe 同目录删除它即恢复初始状态数据文件常见问题杀软拦截、双击无反应、数据库只读对应解决办法5.2 给程序加 --selfcheck 参数打包后的 exe 能自己验证自己main.py 里加一个启动参数分支让 exe 能在命令行执行自检这个技巧对课设交付特别实用。评审开始前自己先在命令行跑一遍几秒钟确认数据库可写、三张表结构正确。def selfcheck(): from db import init_db, get_connection try: init_db() with get_connection() as conn: tables [row[name] for row in conn.execute( SELECT name FROM sqlite_master WHERE typetable).fetchall()] expected {students, courses, scores} print(自检通过已建表, , .join(tables)) return expected.issubset(set(tables)) except Exception as exc: print(自检失败, exc) return False if __name__ __main__: if --selfcheck in sys.argv: sys.exit(0 if selfcheck() else 1) else: main()打包后在命令行执行StudentManagement.exe --selfcheck输出自检通过说明交付物正常。使用说明的「验证方式」一节里写清这条命令并把自检截图附上老师打开压缩包不再需要问「你这程序能跑吗」。本文还有配套的精品资源点击获取
返回列表