简介:这是一套基于Python与MySQL数据库的学生管理系统界面版资源,主要面向正在学习Tkinter窗口编程和数据库增删改查的入门开发者。系统通过Python DB-API连接MySQL服务器,借助Tkinter库构建包含按钮、文本框等组件的图形界面,完整实现学生信息的添加、删除、修改和查询,此外还结合了虚拟环境配置,方便直接运行。整个资源包共754个文件,包括452个py源码文件用于阶梯式研读,140余个pyc编译文件以及58个pyd扩展文件提升模块加载效率,13个exe可执行程序让大家无需配置即可体验功能,同时还附带dll、cfg等运行依赖与配置文件,整体打包约11.97MB,体积小、下载快,非常适合课程设计或毕业设计参考。目前已有2498人参与学习,读者拿到手不仅有完整源码,还有现成的可执行程序和依赖环境,能够快速启动项目,并根据自身需求二次开发一套更完善的学生管理系统。
1. 从“Python界面版学生系统”开始:带界面的学生管理系统该做什么事
把控制台里的学生管理改成界面版,工作量往往超出预期。控制台程序是顺序执行的:输入命令、调用函数、输出结果;而界面版要处理的是事件驱动:用户可能随时点“新增”、点“删除”、关窗口,甚至把成绩留空就提交。这时候状态维护、表单校验、表格刷新和数据库连接释放都成了必须解决的问题。这也是为什么“Python学生管理系统界面版”看起来简单,却是很适合做课程设计和小型项目练手的原因:一个项目同时覆盖 Python 基础语法、SQLite 数据操作和 GUI 事件模型。如果你已经写了不少脚本,但还没系统接触过 tkinter 或 PySide6,这篇文章可以从建表一直带到打包发布。
2. 先分层再写代码:SQLite 数据模型与 tkinter / PySide6 选型
界面版最忌讳的是把增删改查全部塞进按钮的回调函数里。很多学生系统源码看起来能用,但改一个字段要动三个位置,就是因为界面逻辑和业务逻辑混在一起。我的做法是先定好数据模型和业务层,再开始写窗口,这样后面换 GUI 框架也不需要重写数据访问代码。
2.1 选 GUI 框架:tkinter 和 PySide6 在界面版学生系统上的取舍
做学生系统界面版,常见选择是 tkinter 或 PySide6。tkinter 是 Python 自带的标准库,不需要额外安装,在 Windows、macOS 和常用 Linux 桌面环境里都能直接运行。PySide6 是 Qt 的官方 Python 绑定,界面更现代,表格控件更强大,但要先安装再使用,对初学者来说多了环境这一步。
| 框架 | 依赖 | 表格控件 | 适合场景 |
|---|---|---|---|
| tkinter | Python 自带,Linux 下可能需安装 python3-tk | ttk.Treeview | 小型管理类工具、课程设计、快速原型 |
| PySide6 | pip install PySide6 | QTableView + QSqlTableModel | 更复杂的数据展示、需要高级样式和报表 |
如果你的需求只是增删改查加一个统计图,tkinter 足够。如果要做那种带筛选、排序、分页而且数据量可能上万的学生系统,我会直接选 PySide6,因为它的 Model/View 架构可以把数据库表和界面表格绑定起来,减少手动刷新的代码。不要一开始就追求框架新旧,学生系统的核心复杂度在数据一致性而不在界面特效。
2.2 数据层字段与 SQLite 建表
学生系统最常用的数据库是 SQLite,不需要单独安装服务,一个文件就是一个库。字段方面,除了基本的学生信息,我建议把“班级”和“成绩”单独作为字段而不是塞进备注。下面是一份通用建表语句:
CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_no TEXT UNIQUE NOT NULL, name TEXT NOT NULL, gender TEXT DEFAULT '男', class_name TEXT, score REAL, created_at TEXT DEFAULT (datetime('now', 'localtime')) );参数说明:student_no用UNIQUE约束,避免同一个学号被录入两次;score用REAL类型而不是 varchar,否则排序和平均值计算都会变成字符串操作,结果会非常奇怪;created_at让数据库自动写入本地时间,界面不用自己拼日期。这张表足够支撑一个界面版学生系统的大部分操作。
2.3 数据库封装与业务层
窗口里不应该直接写sqlite3.connect和大段 SQL。我一般会把数据库操作封装成一个独立模块,比如db.py,对外只暴露函数。好处是界面层只关心“调用什么函数”,不关心“怎么连接数据库”。
import sqlite3 from contextlib import contextmanager DB_FILE = "students.db" def get_conn(): conn = sqlite3.connect(DB_FILE) conn.row_factory = sqlite3.Row return conn @contextmanager def open_cursor(): conn = get_conn() try: cur = conn.cursor() yield cur conn.commit() finally: conn.close() def add_student(student_no, name, gender, class_name, score): with open_cursor() as cur: cur.execute( "INSERT INTO students (student_no, name, gender, class_name, score) " "VALUES (?, ?, ?, ?, ?)", (student_no, name, gender, class_name, score), ) def list_students(): with open_cursor() as cur: cur.execute("SELECT * FROM students ORDER BY id DESC") return [dict(row) for row in cur.fetchall()]这里的?是 SQLite 的参数化占位符,不是字符串拼接,能有效防止 SQL 注入。open_cursor用 contextmanager 保证无论执行成功还是报错,连接都会关闭。dict(row)把查询结果转换成字典,界面层用row["name"]取值,比row[1]可读性强很多。要注意的是,这个写法对每个操作都会新建和关闭连接,学生系统这种轻量场景没问题,但如果要做高并发访问,就应该改成应用启动时建立一个长连接,这里不展开。
3. 用 tkinter 实现学生系统增删改查:主窗口、表格刷新与 python 环境排查
现在进入界面部分。以下代码基于 tkinter + ttk 实现一个能跑通的最小版本。整个窗口由一个 Treeview 表格和几个按钮组成,数据来源于db.py。
3.1 最小可运行:Treeview 加载数据
先写一个最简窗口类,初始化表格列,然后从数据库读取数据填充。这个版本不需要额外安装任何第三方库,只要 Python 环境正常就能运行。
import tkinter as tk from tkinter import ttk from db import list_students class App(tk.Tk): def __init__(self): super().__init__() self.title("Python学生管理系统界面版") self.geometry("720x400") columns = ("id", "student_no", "name", "gender", "class_name", "score", "created_at") self.tree = ttk.Treeview(self, columns=columns, show="headings") for col in columns: self.tree.heading(col, text=col) self.tree.column(col, width=90, anchor="center") self.tree.pack(fill="both", expand=True) self.refresh() def refresh(self): for item in self.tree.get_children(): self.tree.delete(item) for row in list_students(): self.tree.insert("", "end", values=( row["id"], row["student_no"], row["name"], row["gender"], row["class_name"], row["score"], row["created_at"] )) if __name__ == "__main__": app = App() app.mainloop()参数说明:show="headings"的意思是只显示列标题,不显示最左侧的层级树状列,否则表格左边会多一列空白。anchor="center"让列内容居中,比默认左对齐更接近管理系统的展示习惯。refresh先删除所有旧行再重新插入,这是界面版最简单的刷新方式,虽然没有做增量更新,但在几百条数据下性能完全够用。
3.2 新增与修改:用 Toplevel 弹窗收集表单数据
主窗口上放“新增学生”按钮后,最直接的做法是用tk.Toplevel弹出一个子窗口。下面代码演示新增弹窗,字段包含学号、姓名、性别、班级和成绩。
from tkinter import messagebox from db import add_student def open_add_dialog(self): win = tk.Toplevel(self) win.title("新增学生") win.grab_set() entries = {} fields = [("student_no", "学号"), ("name", "姓名"), ("gender", "性别"), ("class_name", "班级"), ("score", "成绩")] for i, (key, label) in enumerate(fields): tk.Label(win, text=label).grid(row=i, column=0, padx=8, pady=6) entry = tk.Entry(win) entry.grid(row=i, column=1, padx=8, pady=6) entries[key] = entry def submit(): data = {key: entry.get().strip() for key, entry in entries.items()} if not data["student_no"] or not data["name"]: messagebox.showwarning("校验失败", "学号和姓名不能为空") return try: score = float(data["score"]) if data["score"] else None except ValueError: messagebox.showerror("格式错误", "成绩必须是数字") return add_student(data["student_no"], data["name"], data["gender"], data["class_name"], score) win.destroy() self.refresh() tk.Button(win, text="保存", command=submit).grid( row=len(fields), column=0, columnspan=2, pady=10 )这段代码的核心点在grab_set(),它会把输入焦点锁定在弹窗上,用户必须先处理弹窗才能回到主窗口,避免同一时间打开多个新增窗口。entry.get().strip()去掉首尾空格,防止用户输入带空格的学号。成绩允许为空,但非空时必须能被float转换,这里就是 Python 类型转换最常见的应用场景。
修改学生的逻辑与新增类似,区别在于打开弹窗时要先把当前选中行的值填入 Entry。具体可以从self.tree.selection()拿到选中项的 iid,然后用self.tree.item(iid, "values")读取原始数据。
3.3 刷新与排序的坑:不要直接修改 Treeview 的 iid
第一次写界面版学生系统时很容易踩一个坑:在refresh里用self.tree.insert("", "end", values=...)插入数据,然后用self.tree.selection()拿到的选中项去操作数据库。看起来没问题,但如果删除一条记录后没有正确刷新,Treeview 的 item 和数据库行的对应关系就会错乱。
我的建议是插入时显式指定iid为数据库主键:
self.tree.insert("", "end", iid=str(row["id"]), values=(...))这样self.tree.selection()返回的第一个值就是行 ID,删除时直接把这个字符串转成整数传给delete_student(int(iid))。如果不用iid,想定位一行只能遍历 Treeview 的children,代码又多又慢。
排序也一样,不要在 Python 里对 list 排序,直接改 SQL:
SELECT * FROM students ORDER BY class_name ASC, score DESC数据库索引和排序比界面层自己做稳定得多,而且当数据量上来时不容易写错。
3.4 运行前环境:python 安装、vscode 配置与国内源
运行 tkinter 程序时,最常见的报错是在终端输入python后弹出了 Microsoft Store 提示。这通常不是 Python 没安装,而是没有把 Python 加入 PATH,或者没有在编辑器里正确选择解释器。排查顺序是:
python --version which python python -c "import tkinter; print(tkinter.TkVersion)"第一行确认版本,第二行查看实际执行的是哪个解释器,第三行确认 tkinter 可用。在 Windows 上如果第三行报ModuleNotFoundError,大概率是当时安装 Python 时没有勾选 tcl/tk 相关组件。在 Linux 系统上则需要单独安装 python3-tk,Ubuntu 下的命令是sudo apt install python3-tk。
在 vscode 里配置 Python 环境,不要依赖系统默认解释器。按Ctrl+Shift+P,执行 “Python: Select Interpreter”,选择虚拟环境里的 Python,这样终端和调试器用的解释器才一致。需要安装第三方依赖时,可以使用国内镜像源加快速度:
pip install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple如果不想每次输入完整地址,可以设置一次全局源。tkinter 本身不需要 pip 安装,但后面做图表时会用到 matplotlib,这个命令可以提前备着。
4. 向“完整”靠拢:学生系统输入校验、成绩统计、备份和 PyInstaller 打包
一个界面版学生系统光有增删改查还不够,至少要处理“用户乱输入”和“数据安全”这两件事。这一章把校验、统计、备份和打包串起来讲,让项目可以交付出去。
4.1 把字符串转成数字:表单校验的三层防线
Entry 返回的一定是字符串,如果直接把用户输入的“18.5分”存进 score 字段,后期统计平均值时会直接报错。我会设置三层防线。
第一层是界面层校验,用try/except捕获转换异常:
def parse_score(raw): if raw in ("", None): return None try: val = float(raw) except (TypeError, ValueError): raise ValueError(f"成绩无法解析: {raw!r}") if not (0 <= val <= 100): raise ValueError("成绩应在 0~100 之间") return val第二层是业务层,所有入口都调用parse_score,而不是在多个按钮回调里重复写float(...)。第三层是数据库约束:
CREATE TABLE IF NOT EXISTS students ( ... score REAL CHECK (score >= 0 AND score <= 100) );这样即使界面层漏检,数据库也不会接受超出范围的数据。三类校验各管一段,任何一层崩溃都能被上层接住。
4.2 用 matplotlib 嵌入主窗口做班级成绩统计
学生系统的加分项通常是统计功能。先用 SQL 按班级聚合成绩,再画柱状图,这和“python数据分析与可视化”里的思路一致。
SELECT class_name, AVG(score) AS avg_score, COUNT(*) AS cnt FROM students WHERE score IS NOT NULL GROUP BY class_name ORDER BY avg_score DESC画图部分可以用 matplotlib 嵌入 tkinter,而不是单独弹一个窗口。做法是在主界面的右侧放一个FigureCanvasTkAgg:
import matplotlib matplotlib.use("TkAgg") from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure class StatsPanel(ttk.Frame): def __init__(self, master): super().__init__(master) self.fig = Figure(figsize=(4, 3), dpi=100) self.ax = self.fig.add_subplot(111) self.canvas = FigureCanvasTkAgg(self.fig, master=self) self.canvas.get_tk_widget().pack(fill="both", expand=True) def draw(self, class_names, avg_scores): self.ax.clear() self.ax.bar(class_names, avg_scores) self.ax.set_ylabel("平均分") self.canvas.draw()注意matplotlib.use("TkAgg")这句必须在使用 tkinter 创建窗口之前执行,否则画布可能无法嵌入。如果你的数据有更多维度,比如想按性别、班级做层次聚类分析,也可以在这个数据结构上继续扩展,但学生系统一般画到平均分对比就够了。
4.3 参数化查询与数据库备份
删除和更新操作同样要用参数化 SQL:
def delete_student(student_id): with open_cursor() as cur: cur.execute("DELETE FROM students WHERE id = ?", (student_id,))不要把 student_id 直接拼进 SQL 字符串。虽然学生系统看起来没有安全风险,但一旦用户输入内容里带单引号,拼接写法很容易引发 SQL 语法错误甚至注入。参数化是习惯问题,不是看系统大小决定要不要用。
备份数据库最简单的方式是借助 SQLite 的在线备份接口:
import sqlite3 source = sqlite3.connect("students.db") backup = sqlite3.connect("students_backup.db") source.backup(backup) backup.close() source.close()在程序退出前自动备份,可以避免用户误删数据后无法恢复。这个备份操作会复制完整数据库文件,不需要停止应用。
4.4 用 PyInstaller 打包成 exe
交付给没有 Python 环境的机器时,用 PyInstaller 打包是常见做法。基本命令是:
pip install pyinstaller pyinstaller -F -w main.py --name 学生管理系统-F表示打包成单个文件,-w表示不显示控制台窗口,--name指定输出文件名。第一次打包建议在干净环境里执行,不要在自己装了无数依赖的全局环境里打包,否则最终 exe 体积会比实际需要大很多。
打包后要注意students.db文件会生成在程序运行目录,而不是打包后的临时解压目录。不要试图把数据库文件放在 PyInstaller 的内部路径里,用户运行目录不可控,后面要改数据会很麻烦。最简单的做法是首次启动时检查当前目录下是否存在students.db,不存在就自动建库。
5. 验证与打磨:界面版学生系统的回归清单和三个界面细节
5.1 一个完整的回归测试清单
交付前不要只点一遍“新增”就算测完。按照下面这个顺序回归,能覆盖 80% 的常见问题:启动程序后窗口是否能正常显示;新增一条完整学生记录,表格是否立刻刷新;学号留空时点击保存,是否被拦截;成绩输入“abc”时,是否弹出错误提示而不是崩溃;成绩输入空值时是否能正常保存;修改记录后关闭程序重新打开,数据是否还在;删除一条选中记录,是否要求二次确认;快速连续双击多个按钮,是否出现重复窗口或异常数据;打包后的 exe 在另一台没有 Python 的机器上是否能启动。
其中“快速连续双击”最容易暴露问题,因为 tkinter 默认不处理按钮防抖,双击一个按钮可能触发两次新增。如果希望严谨一点,可以在 submit 开始时先禁用保存按钮,保存完成后再恢复。
5.2 三个值得做的界面细节
第一个是使用 ttk 主题统一控件风格:
style = ttk.Style() style.theme_use("clam")clam主题在不同系统上的兼容性比默认主题好,表格的选色和按钮边距也更可控。
第二个是给按钮绑定快捷键。例如删除按钮绑定到Delete键,保存弹窗绑定到Ctrl+Enter。在 tkinter 中可以用bind_all,但要注意在主窗口绑定Delete时,焦点如果在 Entry 输入框里,会先触发输入框的删除行为。我一般会判断事件来源控件:
def on_delete_key(event): if not isinstance(event.widget, ttk.Treeview): return self.delete_selected()第三个是退出时确保数据库连接和备份完成。在App类里重写destroy():
def destroy(self): try: self._backup() finally: super().destroy()这样用户无论点击关闭按钮还是调用quit,都会先备份数据再退出,避免写到一半被强制结束导致students.db损坏。
本文还有配套的精品资源,点击获取