Python+MySQL学生管理系统实战:Tkinter+PyMySQL实现增删改查与可视化

发布时间:2026/10/18 19:28:51
Python+MySQL学生管理系统实战:Tkinter+PyMySQL实现增删改查与可视化 简介这是一套面向Python与数据库初学者的学生管理系统实战项目采用Python编写客户端界面、MySQL负责数据存储适合课程设计、毕业设计或自学练手帮助读者理解桌面应用与数据库的完整交互流程。压缩包共2个文件包含1个py主程序与1个sql数据库脚本前者实现可视化操作界面与业务逻辑后者用于快速建库建表整体仅7KB轻量易部署。系统覆盖学生信息、课程信息、成绩信息及系统管理四大模块支持对学生基本信息、选课情况、考试成绩、平时成绩与总成绩的录入、修改、查询和删除同时可维护课程名称、授课教师、学分等数据功能结构贴近真实教务场景。目前已有6767人学习下载读者可借此掌握Python连接MySQL、界面事件绑定与增删改查的落地写法并在此基础上扩展登录权限、数据校验或报表导出等能力。1. 从一张 Excel 表到可视化系统这个学生管理项目到底解决了什么如果你带过教务或者做过课程设计大概率见过这样的场景学生信息散落在三四个 Excel 文件里成绩表一个、花名册一个、班级名单又一个改一处数据要同步三遍查一个学生的成绩得来回翻。这个基于 Python MySQL 的学生管理系统干的就是把这些散落的表格收进一个带可视化界面的桌面程序里——增删改查学生、录入成绩、按条件筛选全部在一个窗口里完成。它适合两类人一类是刚学完 Python 基础、想找一个能跑起来、能改得动的完整项目练手的新手另一类是需要快速搭一套内部信息管理工具、又不想从零写界面的开发者。技术栈上界面用 TkinterPython 自带零额外依赖数据落 MySQL中间用 PyMySQL 做驱动整体结构清晰改起来不费劲。2. 技术选型与目录结构为什么是 Tkinter PyMySQL 这套组合2.1 界面层为什么选 Tkinter 而不是 PyQt很多人第一反应是 PyQt 界面好看但这个项目选 Tkinter 是有道理的。Tkinter 是 Python 标准库自带的装完 Python 就有不需要额外 pip 安装几百兆的 Qt 运行时。对于一个学生管理系统这种表单密集型应用Tkinter 的ttk.Treeview做数据表格、Entry做输入框、Button做操作按钮完全够用。而且 Tkinter 的布局用grid就能搞定学习成本低新手改起来不会迷路。常见做法是把界面代码和数据操作代码分开不要全塞一个文件里。我一般会这样组织目录student_manager/ ├── main.py # 程序入口启动主窗口 ├── config.py # 数据库连接配置 ├── db/ │ ├── __init__.py │ └── dao.py # 数据访问层所有 SQL 都在这 ├── ui/ │ ├── __init__.py │ ├── main_window.py # 主窗口布局 │ ├── student_frame.py # 学生管理面板 │ └── score_frame.py # 成绩管理面板 └── requirements.txt这样分的好处是改界面只动ui/目录改数据库逻辑只动db/dao.py互不干扰。新手最容易犯的错是把所有代码写进一个main.py写到五百行以后自己都找不到哪段是哪段。2.2 数据层为什么用 PyMySQL 而不是 ORMMySQL 的 Python 驱动有好几个mysql-connector-python是官方的PyMySQL是纯 Python 实现的。这个项目用 PyMySQL原因是它纯 Python、安装简单pip install pymysql一行搞定不需要编译 C 扩展在 Windows 上尤其省事。至于为什么不用 SQLAlchemy 这类 ORM我的看法是这个项目本身就是拿来练手的直接写 SQL 能让你看清楚每条语句到底干了什么。ORM 虽然写起来快但出了问题排查起来多一层黑匣子对新手不友好。等你把这个项目跑通了再去看 ORM 会理解得更透。数据库连接配置单独放config.py# config.py DB_CONFIG { host: localhost, # 数据库地址本机就写 localhost port: 3306, # MySQL 默认端口 user: root, # 改成你自己的用户名 password: your_pwd, # 改成你自己的密码 database: student_db, # 数据库名需要提前建好 charset: utf8mb4 # 支持中文和 emoji }这里charset一定要写utf8mb4不要写utf8。MySQL 里的utf8其实是阉割版存不了某些生僻字和 emoji学生姓名里如果有生僻字就会报错或者存成问号。这是血泪经验我踩过。2.3 数据库表结构设计建库建表是第一步SQL 如下-- 建库 CREATE DATABASE IF NOT EXISTS student_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE student_db; -- 学生表 CREATE TABLE IF NOT EXISTS student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE, -- 学号唯一 name VARCHAR(50) NOT NULL, -- 姓名 gender ENUM(男,女) DEFAULT 男, -- 性别 age INT, -- 年龄 class_name VARCHAR(50), -- 班级 phone VARCHAR(20) -- 联系电话 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 成绩表 CREATE TABLE IF NOT EXISTS score ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL, -- 关联学号 course VARCHAR(50) NOT NULL, -- 课程名 score DECIMAL(5,1), -- 成绩保留一位小数 FOREIGN KEY (sno) REFERENCES student(sno) ON DELETE CASCADE -- 删学生时级联删成绩 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个关键点说明一下。sno加了UNIQUE约束防止重复录入学号成绩表的sno是外键ON DELETE CASCADE意味着删掉一个学生他的成绩记录会自动跟着删不用手动清理。score用DECIMAL(5,1)而不是FLOAT因为浮点数存成绩会出现89.99999这种玄学问题DECIMAL精确。3. 核心模块拆解从数据库连接到界面刷新的完整链路3.1 数据访问层把 SQL 关进一个文件里db/dao.py是整个项目的数据中枢所有 SQL 都在这。先看连接和查询部分# db/dao.py import pymysql from config import DB_CONFIG def get_conn(): 获取数据库连接每次操作完记得关闭 return pymysql.connect(**DB_CONFIG) def query_all_students(): 查询所有学生返回列表 conn get_conn() try: with conn.cursor(pymysql.cursors.DictCursor) as cur: cur.execute(SELECT * FROM student ORDER BY sno) return cur.fetchall() # 返回 [{sno:..., name:...}, ...] finally: conn.close() def add_student(sno, name, gender, age, class_name, phone): 新增学生返回 True/False conn get_conn() try: with conn.cursor() as cur: cur.execute( INSERT INTO student(sno,name,gender,age,class_name,phone) VALUES(%s,%s,%s,%s,%s,%s), (sno, name, gender, age, class_name, phone) ) conn.commit() return True except pymysql.err.IntegrityError: # 学号重复会触发唯一约束错误 return False finally: conn.close()逻辑说明DictCursor让查询结果以字典形式返回字段名直接当 key 用比默认的元组好读得多。add_student里捕获了IntegrityError这是学号重复时 MySQL 抛的异常捕获后返回False上层界面就能提示学号已存在。参数说明sno是学号字符串age传整数score传浮点数即可PyMySQL 会自动处理类型转换。注意finally: conn.close()这个写法。数据库连接是有限资源不关的话连接数会越积越多最后 MySQL 报Too many connections。我一般会把连接管理封装成上下文管理器但新手项目里用try/finally最直观。3.2 界面层Treeview 表格 表单的联动主界面用ttk.Treeview展示学生列表上面放输入框和按钮。核心代码# ui/student_frame.py import tkinter as tk from tkinter import ttk, messagebox from db import dao class StudentFrame(ttk.Frame): def __init__(self, master): super().__init__(master) self.pack(fillboth, expandTrue) self._build_form() self._build_table() self.refresh_table() # 启动时加载数据 def _build_form(self): 顶部输入区 frm ttk.Frame(self) frm.pack(fillx, padx10, pady8) labels [学号, 姓名, 性别, 年龄, 班级, 电话] self.entries {} for i, text in enumerate(labels): ttk.Label(frm, texttext).grid(row0, columni*2, padx4) ent ttk.Entry(frm, width12) ent.grid(row0, columni*21, padx4) self.entries[text] ent ttk.Button(frm, text新增, commandself.on_add).grid( row0, column12, padx8) def _build_table(self): 中间表格区 cols (sno, name, gender, age, class_name, phone) self.tree ttk.Treeview(self, columnscols, showheadings) headers [学号, 姓名, 性别, 年龄, 班级, 电话] for col, head in zip(cols, headers): self.tree.heading(col, texthead) self.tree.column(col, width100, anchorcenter) self.tree.pack(fillboth, expandTrue, padx10, pady8) def refresh_table(self): 清空表格并重新加载数据 for row in self.tree.get_children(): self.tree.delete(row) for stu in dao.query_all_students(): self.tree.insert(, end, values( stu[sno], stu[name], stu[gender], stu[age], stu[class_name], stu[phone] )) def on_add(self): 点击新增按钮的回调 data {k: v.get().strip() for k, v in self.entries.items()} if not data[学号] or not data[姓名]: messagebox.showwarning(提示, 学号和姓名不能为空) return ok dao.add_student( data[学号], data[姓名], data[性别], int(data[年龄] or 0), data[班级], data[电话] ) if ok: messagebox.showinfo(成功, 添加成功) self.refresh_table() for ent in self.entries.values(): ent.delete(0, end) # 清空输入框 else: messagebox.showerror(失败, 学号已存在)逻辑说明_build_form用循环生成六个输入框grid布局按列排开比手写六遍Label Entry清爽。refresh_table先删掉表格里所有旧行再重新插入这是最简单的刷新方式数据量小的时候完全没问题。on_add里先做非空校验再调dao.add_student成功就刷新表格并清空输入框。参数说明tree.insert(, end, values(...))里第一个参数是父节点空字符串表示根节点end表示插到末尾。int(data[年龄] or 0)这个写法是防止年龄留空时int()报错留空就当 0 处理。3.3 主窗口组装与程序入口main.py负责把各个面板拼起来# main.py import tkinter as tk from tkinter import ttk from ui.student_frame import StudentFrame def main(): root tk.Tk() root.title(学生管理系统) root.geometry(900x600) notebook ttk.Notebook(root) notebook.pack(fillboth, expandTrue) # 学生管理页 student_page StudentFrame(notebook) notebook.add(student_page, text学生管理) root.mainloop() if __name__ __main__: main()用ttk.Notebook做标签页学生管理和成绩管理各占一页后面要加新功能直接notebook.add就行。root.geometry(900x600)设了初始窗口大小太小表格显示不全太大在笔记本上又占满屏900x600 是个比较舒服的值。4. 跑起来之前环境搭建与常见问题排查4.1 环境准备与启动步骤先把依赖装上# 建议用虚拟环境避免污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS/Linux 激活 source venv/bin/activate # 安装依赖 pip install pymysql然后确认 MySQL 服务在跑用命令行或者 Navicat 连上去把第 2 章那套建库建表 SQL 执行一遍。最后改config.py里的用户名密码运行python main.py窗口应该就出来了。4.2 常见问题排查现象一启动报pymysql.err.OperationalError: (2003, Cant connect to MySQL server)。原因MySQL 服务没启动或者host/port写错了。解决Windows 上在服务里找 MySQL 启动macOS 用brew services start mysqlLinux 用systemctl start mysql。确认端口是 3306如果你装 MySQL 时改过端口config.py里也要跟着改。现象二中文显示成乱码或者问号。原因建库时字符集用了utf8而不是utf8mb4或者连接配置里没写charset。解决config.py里charset必须是utf8mb4建库语句也要用utf8mb4。已经建好的库可以ALTER DATABASE student_db CHARACTER SET utf8mb4;改过来。现象三点新增按钮没反应也不报错。原因大概率是on_add里某个Entry取值时抛了异常被吞了或者按钮的command没绑上。解决在on_add开头加print(data)看有没有执行到检查ttk.Button的commandself.on_add有没有写漏括号写self.on_add()就错了会立即执行。现象四表格里数据重复出现。原因refresh_table忘了先清空旧数据每次刷新都往后面追加。解决确认refresh_table开头有for row in self.tree.get_children(): self.tree.delete(row)这两行。现象五删学生时报外键约束错误。原因成绩表里有这个学生的成绩记录而外键没设ON DELETE CASCADE。解决建表时加上级联删除或者删学生前先手动删成绩。已经建好的表可以ALTER TABLE score DROP FOREIGN KEY 外键名;再重新加带CASCADE的外键。提示改完config.py后不用重启 MySQL但改完表结构后程序要重新跑一遍因为连接是启动时建立的。5. 二次开发方向从单机版到能用的工具还差什么这个项目跑通之后你会发现它离真正能用还有一段距离。下面说几个我实际改过的方向按难度从低到高排。第一加搜索和筛选。现在只能看全部学生数据一多就不好找。在StudentFrame顶部加一个搜索框refresh_table改成接收一个关键字参数SQL 里加WHERE name LIKE %s OR sno LIKE %s。这个改动半小时能搞定但体验提升很大。第二加成绩管理面板。照着StudentFrame的结构再写一个ScoreFrame表格列换成学号、课程、成绩新增时校验学号是否存在。这里有个坑成绩录入时如果学号在student表里不存在外键会报错所以要先查一下再插。第三把密码从明文改成哈希。如果这个系统要给多个老师用得加个登录界面用户表里密码不能明文存。用hashlib.sha256加盐就行不用上 bcrypt 那么重。改的时候注意注册时存哈希登录时把输入的密码哈希后比对。第四导出 Excel。教务那边经常要交表格加一个导出按钮用openpyxl把Treeview里的数据写进 xlsx。核心就三行建 workbook、遍历tree.get_children()写行、wb.save()。第五打包成 exe。用pyinstaller -F -w main.py打成单文件双击就能跑不用装 Python。注意-w是去掉控制台窗口但调试阶段别加不然报错你看不见。打包后如果连不上数据库检查config.py是不是被打进去了路径要用绝对路径或者配置文件外置。验证改动有没有生效我一般会走一遍这个流程先手动在 MySQL 里SELECT一下确认数据对再在界面上操作一遍看表格刷新对不对最后关掉程序重开看数据还在不在。三步都过了基本就没问题。从那以后我每次改完数据层的代码都强制先在 MySQL 命令行里把 SQL 单独跑一遍确认语句本身没问题再去调界面。这个习惯帮我省了很多到底是 SQL 错了还是界面没刷新的排查时间。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询