简介:PyQt5学生信息管理系统是一份面向Python初学者和毕业设计者的完整源码包,基于PyQt5实现学生信息的增删改查,整合pymysql、xlwt、MySQL及QtDesigner/pyuic工具链,适合用作桌面端GUI开发入门练手项目。压缩包共34个文件,包含8个Python脚本、7个Qt Designer生成的ui界面、对应pyc编译文件、4个xml配置、2个MySQL数据库sql脚本,以及图片、图标、部署调试说明等,整体仅257KB,结构轻量清晰。已有466人学习下载。除了可运行的代码外,资源还附带数据库初始脚本和部署调试服务说明,能帮助读者快速搭建环境并理解PyQt5与MySQL连接的完整流程,同时通过ui与py的对应关系掌握pyuic转换和界面逻辑分离的基本写法,对完成课程设计或熟悉Qt开发流程具有直接参考价值。
1. PyQt5 学生信息管理系统:一套能跑通答辩和课设的现成骨架
面对 PyQt5 学生信息管理系统这类 Python 毕业设计源码包,很多人第一反应是双击 main.py 把它跑起来,结果十有八九翻车。这套资源说白了就两部分:Python 源码 + SQL 脚本,界面用 PyQt5 画,数据落在 MySQL,中间用 PyMySQL 建立连接,覆盖登录、增删改查、模糊查询和统计这一类课设标准功能。我的建议是别急着启动,先把数据库脚本导进去、把工程结构过一遍,再运行,半小时就能把环境踩平。适合正在赶课设、或者想拿现成骨架做二次开发的从业者,也适合答辩前想补课的新手。
2. 源码结构拆解:一个文件一个职责,先看清再动手
拿到压缩包先别急着跑,先看目录。这类课设工程的成熟模板一定不会把所有逻辑塞进一个文件——否则评审老师随口问一句「你这个查询结果是哪个方法刷出来的」你就答不上来。我拆这类工程时,习惯先按「界面层、业务层、数据层」三条线把文件归好类,再考虑运行。下面这套结构几乎所有 PyQt5 课设工程都会长成这样,差异只在命名。
2.1 三层模块划分:界面、业务、数据各管一摊
student_sys/ ├── main.py # 程序入口,只负责启动 ├── db.py # MySQL 连接与通用查询封装 ├── ui/ │ ├── login_window.py # 登录窗口 │ └── main_window.py # 主窗口(含所有功能页) ├── styles/ │ └── app.qss # 界面样式表 └── sql/ └── student_sys.sql # 建库建表脚本main.py 是入口,它做的事只有三件:加载配置、实例化登录窗口、进入 Qt 事件循环。db.py 承担所有 SQL 访问,登录验证、学生增删改查都通过它转发。ui 目录下两个文件分别是登录框和主窗体,主窗体里用 QTabWidget 或 QStackedWidget 承载多个功能页。styles 目录下的 QSS 文件控制全局配色,sql 目录里则是完整建库脚本。
如果想把功能页拆得更细,常见做法是每个 Tab 页单独成一个文件,例如 student_page.py、score_page.py。对于 2000 行以内的课设工程,我建议不要拆太碎,否则改一个字段要跨三个文件追,反而麻烦。这套资源如果只有一个 main_window.py,也别觉得简陋,课程设计阶段「能跑、能讲、能改」比「架构炫」优先得多。
2.2 信号槽:按钮和表格之间是怎么联动起来的
PyQt5 里最容易被答辩老师追问的就是信号槽机制。按钮被点击、表格行被双击、输入框内容变化,这些「事件」通过信号广播,你的处理方法通过槽函数接住。下面是主窗口里最常见的三组连接:
# 按钮点击事件绑定 self.btn_search.clicked.connect(self.on_search) self.btn_add.clicked.connect(self.on_add) # 表格双击事件,双击某一行直接进入编辑 self.table.doubleClicked.connect(self.on_edit)clicked 是 QPushButton 内置信号,on_search 是当前类里自定义的槽函数。connect 做的事情是「监听信号 → 收到后调用槽函数」。这里有两个易错点:一是槽函数不要加括号,self.on_search()会在程序启动时立刻执行一次而不是点击时执行;二是 doubleClicked 信号会携带一个 QModelIndex 对象,所以 on_edit 的定义要写成def on_edit(self, index),方法签名的参数数量要和信号参数对上,否则运行时报错。
我接手过的工程里,最常见的低级错误是把所有按钮逻辑写成if __name__ == "__main__"里的一长串。那不是信号槽,是脚本。信号槽的价值在于解耦:界面只管发信号,业务逻辑只待在槽函数里,后期加按钮、加功能不需要动结构。
2.3 主窗体到子页面的切换方式
主窗体里通常有多个功能页面,切换方式直接决定代码复杂度。推荐用 QStackedWidget 做页面堆叠,而不是每次点击都打开一个新窗口。新窗口弹来弹去,学生信息还没看完,窗口管理器先崩了。
# 切换到第 2 个页面(例如成绩查询页) self.ui.stackedWidget.setCurrentIndex(1)setCurrentIndex 接收页面的下标,从 0 开始。用这个方式切换页面,数据状态保留得最完整——从查询页切到编辑页再切回来,表格内容还在原地。另一个常见方案是 QTabWidget,适合功能 Tab 不用来回藏的场景。登录窗口切到主窗口则一般这么衔接:
self.login_success = True self.main_window = MainWindow() self.main_window.show() self.close()注意 close 之后进程不会退出,因为登录窗口的引用还在 Qt 事件循环里。除非你想做「关闭登录框后整个程序退出」的假登录效果,否则不要用sys.exit去关登录窗口,那会让主窗口一起消失。
3. 环境搭建与数据库初始化:版本、字符集、授权三个变量
拖垮一个课设工程的有三件事:Python 版本不对、SQL 脚本没有正确导入、数据库账号连不上。这三件事的失败表现都极其相似——窗口起不来,或者一登录就报pymysql.err.OperationalError。所以我把环境搭建单独拆出一章,先把这条链路焊死,再谈功能。
3.1 依赖安装:PyQt5 与 PyMySQL 的版本边界
pip install PyQt5==5.15.10 PyMySQL==1.1.0PyQt5 5.15 系列是最后一批广泛兼容的版本,新项目我一般锁在这个版本上,不要轻易追新。PyMySQL 是纯 Python 实现的 MySQL 驱动,不需要本地编译,在 Windows 上安装最省事。我在 Python 3.10、3.11 上都跑过这一套组合,没有兼容性问题。注意别混装 PyQt5 和 PyQt6,按钮、信号槽 API 有差异,课设工程改了包名很容易牵一发动全身。
安装完成后验证一下:
python -c "import PyQt5; import pymysql; print('ok')"如果这里报 ModuleNotFoundError,说明 pip 装到了别的 Python 环境。这个现象在 macOS 和 Linux 上尤其常见,系统自带的 Python 和当前终端用的 Python 不是同一个。检查pip --version和python --version是否对得上,用同一个解释器再装一遍。
3.2 导入 SQL 脚本:utf8mb4 与账户授权
打开 sql 目录下的 student_sys.sql,你先看到的一般是建库语句:
CREATE DATABASE IF NOT EXISTS student_sys DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_sys; CREATE TABLE t_user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(32) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL, role VARCHAR(8) DEFAULT 'admin' ) ENGINE=InnoDB; CREATE TABLE t_student ( id INT AUTO_INCREMENT PRIMARY KEY, sno VARCHAR(16) UNIQUE NOT NULL, name VARCHAR(32) NOT NULL, gender VARCHAR(4), class_name VARCHAR(32), phone VARCHAR(16), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB;建库时指定utf8mb4字符集是这批资源最值得保留的习惯。utf8mb4 是 MySQL 对 UTF-8 的完整实现,能直接存中文,也能兼容表情符号;只写 utf8 会遇到四字节字符入库报错的问题。导入脚本可以用命令行:
mysql -u root -p < student_sys.sql或者进入 MySQL 后用 source 命令:
mysql> source C:/path/to/student_sys.sql;source 的路径在 Windows 下用正斜杠写,复制文件资源管理器里的反斜杠路径进去会找不到文件。导入后检查三件事:SHOW CREATE TABLE t_student确认字符集;SELECT COUNT(*) FROM t_student确认初始数据存在;SELECT * FROM t_user确认管理员账号已经预置。这三步对应后续登录能不能进去、列表有没有数据、账号对不对,提前验证省得后面猜。
3.3 数据库配置读取:路径和账号写在哪里才是安全的
这套工程的数据库连接配置一般集中在 db.py 或一个 config 文件里。课设阶段我最推荐直接写一个字典:
# db.py import pymysql config = { "host": "localhost", "port": 3306, "user": "root", "password": "123456", "database": "student_sys", "charset": "utf8mb4" } def get_conn(): return pymysql.connect(**config)host 是localhost时,MySQL 服务必须在本机启动,不能是远端数据库地址。port 默认 3306,如果你本机 MySQL 改过端口,这里不改的话会报Connection refused。password 默认写 123456 这类初始密码,实际导入后你应该用ALTER USER 'root'@'localhost' IDENTIFIED BY '你的密码';改掉,并且同步改这个字典。
密码直接写死在代码里在真实项目里是忌讳,但毕业设计场景里,答辩老师反而会问「为什么不用配置文件」。所以我的习惯是:源码里保留 config 字典,注释里写清楚「上线前抽到 config.ini」,给出如下拆分方案:
# config.ini [mysql] host = localhost port = 3306 user = root password = 123456 database = student_sys charset = utf8mb4读取时用configparser解析,这样打包后改库密码不需要重新编译代码。不过课设阶段,直接字典改成自己本机的账号密码,比折腾配置文件读取更快。
3.4 首次启动验证:哪些表现说明环境搭对了
环境搭完第一次启动,不要直接点登录。先看有没有异常抛出,再判断是不是配置问题。我列了一个自检表,对应每种表现的各种可能性:
| 启动表现 | 可能原因 | 处理方式 |
|---|---|---|
窗口正常出现,登录报Access denied | 账号或密码错误 | 用命令行mysql -u root -p验证账号可登录 |
窗口正常出现,登录报Unknown database | 库名不一致 | 检查 config 里的 database 是否与建库语句一致 |
| 窗口一闪而过直接退出 | 缺依赖或代码里抛异常 | 在终端里运行python main.py,看完整报错 |
| 界面无样式 | QSS 文件路径错误 | 检查 styles.py 里的相对路径是否基于工程根目录 |
我见过最气人的一种情况:数据库没启动,窗口能开,一点登录直接崩。在 Windows 上可以到「服务」里确认 MySQL 是否处于运行状态,macOS 则用brew services list查看。这一章整体走完,后面所有功能调试才有一个稳定的地基。
4. 核心功能闭环:登录、增删改查与模糊查询的对接
学生信息管理系统的功能面说起来就是一条线:登录进去、看列表、按条件查、增一条、改一条、删一条。代码里界面层和 SQL 层一一对应,把这条线串通,就完全够答辩演示了。下面按功能顺序逐个拆。
4.1 登录校验:参数化查询与角色返回
登录窗口拿到输入框的账号和密码,不能直接拼 SQL 字符串,必须走参数化查询。常见做法是把校验封装成一个方法,返回用户角色或者 None:
def check_login(self, username, password): sql = "SELECT id, role FROM t_user WHERE username=%s AND password=%s" row = self.db.fetch_one(sql, (username, password)) return row%s是占位符,PyMySQL 会把元组里的值安全地填进去,这是防 SQL 注入的标准姿势。如果写成"WHERE username='" + username + "'",输入框里加个引号就能把 SQL 结构拆掉,这个坑在答辩现场属于低端翻车,必须避免。fetch_one 返回的是字典还是元组取决于 db.py 里 cursorclass 的设置,这套资源如果你发现row["role"]报 TypeError,就去 db.py 把 cursorclass 改成pymysql.cursors.DictCursor。
role 这个字段是有用的,不只是摆设。管理员账号和学生账号可以共用一张用户表,登录后根据 role 决定主窗口里「新增/删除」按钮是否可点击。界面隐藏不等于权限安全,但课设层面已经够讲。
4.2 表格填充:从查询结果到 QTableWidget
列表展示是学生信息系统的门面。查询接口返回一批记录,要把它们填进 QTableWidget,关键在于先清空旧数据再整表重建:
def load_table(self, rows): self.table.setRowCount(0) for i, row in enumerate(rows): self.table.insertRow(i) self.table.setItem(i, 0, QTableWidgetItem(str(row["sno"]))) self.table.setItem(i, 1, QTableWidgetItem(row["name"])) self.table.setItem(i, 2, QTableWidgetItem(row["gender"]))setRowCount(0)先重置行数,防止上一次查询的残留行继续显示。insertRow(i)每插入一行前先申请行号,然后setItem按列填值。注意学号虽然可能是字符串,但强烈建议str()包一层,避免数据库里存了数字类型导致 QTableWidgetItem 类型报错。
这个方法不适合大数据量,5000 行以上会明显卡顿。课设几千行的数据量没问题,但如果演示时硬塞 2 万条进去,界面刷新会有半秒左右的白屏,这不是 bug,是逐单元格写入的固有瓶颈。防止卡顿的办法是后面避坑章里讲的setRowCount一次性分配合并行数,这里先记个印象。
4.3 增删改:事务与确认弹窗
新增和编辑走同一个逻辑框架:弹一个对话框收集字段 → 组装 INSERT 或 UPDATE 语句 → 刷新表格。删除则必须有确认弹窗,这是课设演示的加分项,也是防止手滑删数据的防线:
def on_delete(self): reply = QMessageBox.question( self, "确认删除", "确定要删除当前选中的学生吗?该操作不可恢复。", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.db.execute( "DELETE FROM t_student WHERE id=%s", (self.current_row_id,) ) self.load_data()删除条件一定要用主键id,不能用学号、姓名。学号理论上唯一,但实际库里可能因为早期数据不规范存在重复,用姓名删除更是灾难。current_row_id 应该在双击表格时从选中行取到并暂存:
def on_edit(self, index): self.current_row_id = self.table.item(index.row(), 0).data(32)item(...).data(32)是读取 QTableWidgetItem 的原始数据,比.text()更稳,避免显示格式和存储值不一致导致删错行。这个细节讲给答辩老师听,对方会认为你真踩过数据库的坑。
4.4 模糊查询:LIKE 拼接与输入处理
模糊查询是演示中最容易被现场造数据的环节。查询框里输入姓名或班级,列表实时过滤。SQL 的写法核心是「动态条件拼接」:
def search(self, name_input, class_input): sql = "SELECT * FROM t_student WHERE 1=1" params = [] if name_input: sql += " AND name LIKE %s" params.append(f"%{name_input}%") if class_input: sql += " AND class_name LIKE %s" params.append(f"%{class_input}%") self.load_table_with_sql(sql, params)WHERE 1=1是一条惯用技巧,它让后续所有AND条件都能直接追加,不需要判断「是不是第一个条件」。LIKE %s配合%关键字%实现包含匹配:%在 SQL 里是通配符,代表任意长度字符。name_input 必须经过 f-string 包一层%,而不是直接在 SQL 里写LIKE '%name_input%',否则连模糊查询的字面意思都无法实现。
两个输入框同时有值时,params 列表里两个参数依次对应 SQL 中两个占位符,顺序和 SQL 拼接顺序一致。搜索按钮连击多次不会累积条件,因为每次都是重新组装 sql 变量。唯一要注意的是输入框里如果带了_或%字符,会被当作通配符处理,这个细节放到避坑章单独说。
5. 避坑与常见问题排查:五条血泪踩坑记录
前面几章把工程结构和功能链路打通了,这一章专门讲我实际调试这类课设工程时踩过的坑。每一条都按「现象 → 原因 → 解决」写清,照着比对即可。
5.1 中文乱码:建库时字符集没定对
现象:程序能跑,新增学生后姓名在界面上正常显示,但去 MySQL 命令行查表,中文全部变成??或者直接消失。
原因:数据库连接字符串里没有charset="utf8mb4",或者建库语句没指定字符集。MySQL 默认字符集在部分安装环境里是 latin1,中文当然存不进去。
解决:两处同时改——建库脚本里用DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,连接参数里加上"charset": "utf8mb4"。如果是已经建好的库,用ALTER DATABASE student_sys CHARACTER SET utf8mb4;补救。改完后重启程序再试,不要只改一处。
5.2 QSS 不生效:样式加载时机和路径问题
现象:界面元素齐全、按钮能点,但颜色、圆角、字体完全没有,整个窗口灰扑扑的。
原因:app.qss 文件没有被加载,或者加载时机早于窗口实例化,样式被后续默认样式覆盖。相对路径写错更是常见,窗口类里open("app.qss")用的是当前工作目录,在终端直接运行和打包后运行的工作目录往往不是同一个。
解决:加载路径基于工程根目录动态拼,并确保在窗口实例化之后立即应用:
import os, sys qss_path = os.path.join(os.path.dirname(sys.argv[0]), "styles", "app.qss") with open(qss_path, encoding="utf-8") as f: self.setStyleSheet(f.read())sys.argv[0]取到的是入口脚本所在目录,不管从哪个目录启动都不会走偏。注意open要指定encoding="utf-8",Windows 下 QSS 文件里如果含中文注释,不指定编码会直接抛 UnicodeDecodeError。
5.3 打包后双击没反应:PyInstaller 吞了错误
现象:开发环境运行正常,用 PyInstaller 打包后双击 exe 毫无反应,进程一闪而过或压根不出现。
原因:-w参数隐藏了控制台窗口,所有异常信息也一起被吞了,程序启动即崩溃但你什么都看不见;或者是 PyMySQL、PyQt5 的某些动态库没被正确打进包里。
解决:第一轮打包先不加-w,用python -m PyInstaller -F main.py生成带控制台的版本,双击后看终端里的完整报错。报错指向哪就补哪,缺包就加--hidden-import。常见需要补的:
pyinstaller -F -w --hidden-import pymysql --hidden-import PyQt5.QtSvg main.py打包后还需要把 styles 目录和 sql 目录复制到 exe 同目录。PyInstaller 的-F只打包 Python 字节码和依赖库,外部资源文件必须手动带。这也是我建议把 QSS 路径写成基于sys.argv[0]的原因——打包后工作目录会变,写死相对路径必挂。
5.4 查询卡顿:QTableWidget 逐行 insertRow 太慢
现象:数据量到两三千行,每次刷新表格要卡一两秒,点击搜索按钮明显有停顿感。
原因:insertRow每调用一次就要触发表格重排,2000 行等于重排 2000 次。这是 QTableWidget 的典型性能玄学,初学者最容易踩上。
解决:一次性setRowCount分配合适行数,再逐行setItem,一次批量填充:
self.table.setRowCount(len(rows)) for i, row in enumerate(rows): self.table.setItem(i, 0, QTableWidgetItem(str(row["sno"]))) self.table.setItem(i, 1, QTableWidgetItem(row["name"]))另外在数据量大的表上,给数据库查询加索引也有效。t_student 表如果经常按 name 查,建一个普通索引就行:CREATE INDEX idx_student_name ON t_student(name);两件事一起做,几千行的量级能到毫秒级刷新。
5.5 输入框里的 % 和 _ 被当通配符:搜索跑偏
现象:搜索框输入%或者_,列表返回所有数据,明明只输了一个字符却像没过滤一样。
原因:这两个字符在 LIKE 语句里是通配符,%匹配任意串,_匹配单个字符。用户搜索字符串里如果包含这些符号,不转义就会被当成语法的一部分。
解决:把%、_、\三个字符做转义,再把转义后的关键字传入 LIKE 条件:
def escape_like(s): return s.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")SQL 里写成name LIKE %s ESCAPE '\\',告诉 MySQL 用什么字符作为转义标记。这一招在答辩现场遇到较真的老师提问「你的搜索有注入风险吗」时非常加分。别觉得输入%是无聊场景,评审现场真的有人会这么试。
6. 答辩演示前的十分钟验证清单:把翻车空间压到零
6.1 固定顺序检查:从数据库到界面
演示前一天,我一般不再改任何代码,只做一遍「固定顺序验证」。顺序是:确认 MySQL 服务启动 → 用命令行登录数据库确认账密可用 → 运行程序进入主界面 → 点一次查询按钮确认列表有数据 → 执行「新增 → 修改 → 删除」最小闭环。每个环节录一个预期结果,和实际表现比对:
| 检查步骤 | 预期结果 |
|---|---|
| MySQL 服务启动 | 服务状态为 running |
| 命令行登录 | 能进 mysql 命令提示符 |
| 程序登录 | 无异常,进入主窗口 |
| 点击查询 | 表格出现初始数据 |
| 新增一条记录 | 表格末行出现新记录 |
| 删除该记录 | 表格行数回落 |
这六步走完,环境、依赖、数据库、代码四大环节就都有了真实证据。演示时最忌讳的是一边打开程序一边手动 import 数据库或改配置,现场任何一个不确定项都会放大为灾难。
6.2 数据复位脚本:演示的后悔药
课设答辩少不了现场演示新增、修改、删除,演示完数据往往已经一团糟。我的习惯是在 sql 目录里放一个 reset.sql,里面只有三条语句:清空业务表、重置自增值、重新插入初始数据。
USE student_sys; SET FOREIGN_KEY_CHECKS = 0; TRUNCATE TABLE t_student; SET FOREIGN_KEY_CHECKS = 1; INSERT INTO t_student (sno, name, gender, class_name, phone) VALUES ('20230001', '示例学生', '男', '计科2301', '13800000000');模拟数据复位后再跑一遍主界面查询,列表就回到了演示前的样子。我不会在演示时真的执行这个脚本,但会把它放在桌面上——万一演示到一半出了岔子,「数据恢复」就是最快下台阶的路。
那一次,我在演示查询功能时输入框漏了半角空格,列表直接空白,台上愣了几秒才反应过来是输入问题。从那以后,我每交付一个课设工程,都会强制把这个验证清单和 reset.sql 塞进工程根目录——不是给代码看的,是给演示前的自己看的。希望帮到你。
本文还有配套的精品资源,点击获取