资讯详情

资讯详情

基于Python的家庭记账本毕设实战:数据建模、GUI开发与答辩经验

每年到了毕业季总会有学弟学妹跑来问我同一个问题Python相关的毕设题目选什么比较稳我的回答通常很直接——基于Python的家庭记账本设计与实现看着不起眼但它在“能顺利通过答辩”和“拿得出手”之间取了一个非常好的平衡。账本这个题目覆盖了数据库设计、GUI开发、数据统计、可视化展示这些毕设考察的核心点难度可控又不需要任何外部硬件和企业级中间件是一个只要认真做就不会翻车的项目方向。这篇文章我把整个项目的设计思路、数据表结构、核心功能实现、打包和答辩经验完整整理出来给正在选题或已经选了记账本方向的朋友做个参考。这个项目适合谁一类是需要完成毕设的在校生想要一个结构清晰、逻辑完整、可复现的源码型项目另一类是刚学完Python基础、想找一个真实项目来练手的人。无论你是哪一种只要能跟着把数据模型和核心逻辑理顺你收获的东西会远超代码本身——你会真正理解一个带界面的应用是怎么从零到一搭起来的。1. 为什么把家庭记账本当毕设项目是最稳妥的选择之一1.1 技能覆盖面与答辩可控性家庭记账本这个题目看似“烂大街”但烂大街恰恰是它最大的优点。毕设答辩的核心考察点其实很固定数据库建模是否合理、功能模块是否完整、代码逻辑是否清晰、界面是否能正常演示。这四个点记账本项目全部覆盖而且每一项都不会难到失控。具体拆开来看这个项目做下来能训练到的技能非常均衡数据库设计多张表的关系建模、主外键约束、常见的聚合查询。GUI开发窗口布局、事件绑定、表格控件、弹窗交互这些都是桌面开发的基础功。文件与数据处理金额的精度处理、日期格式处理、数据导入导出和备份。数据可视化饼图、柱状图、折线图把流水变成用户一眼能看懂的统计结果。异常处理与健壮性非法输入拦截、空数据显示占位、删除操作二次确认等。更关键的是“答辩可控性”。你要知道答辩现场最怕的不是被问住而是被问住的点特别专业、特别深。比如有人做了个算法优化类的题目评委追问一句“你这里的时间复杂度为什么是O(n log n”答不上来场面就很难看。但记账本这个项目评委大概率会问“为什么用SQLite不用MySQL”“金额为什么用Decimal”“预算超支是怎么判断的”这类问题——这些问题只要是自己真正做的就一定能答得上来。1.2 技术路线确认PySide6 SQLite Matplotlib 的取舍技术选型是个容易被低估的步骤很多同学一上来就写代码写到一半发现GUI框架不合适推倒重来。我在这个项目里最终确定的技术栈是Python 3.10 PySide6 SQLite Matplotlib。这个组合不是随便拍的下面逐一说下取舍逻辑。第一GUI框架选PySide6而不是tkinter或PyQt5。tkinter虽然内置、零依赖但做出来的界面确实丑控件风格偏老旧答辩时视觉效果比较吃亏。PyQt5和PySide6功能对等但PySide6是官方支持的Python绑定版权许可更友好而且信号槽语法比PyQt5更清晰。最实际的一点是很多评委见过PyQt5项目看到PySide6反而会觉得你技术栈跟得比较新。第二数据库用SQLite而不是MySQL。曾经遇到过一个同学非要在毕设里装MySQL服务结果答辩前在现场连不上数据库整个演示直接崩了。SQLite是文件型数据库单文件、零配置、随应用走数据备份就是复制文件这对毕设演示来说是压倒性的优势。尤其是答辩用教室的电脑你不能指望对方机器上装好了MySQL。第三图表用Matplotlib而不是pyecharts或Plotly。pyecharts会在浏览器里渲染桌面应用里调用它需要嵌浏览器组件复杂度直接上一个台阶。Matplotlib本身就能输出高质量的统计图嵌入Qt窗口里的社区方案非常成熟代码量小、可控性强。这三个选择合在一起最终形成的是一个完全离线、可单机运行、数据自包含的项目。这意味你在任何一台Windows电脑上都能稳定演示而稳定演示这四个字在答辩中是第一优先级。2. 数据模型先行记账需求如何拆成表结构2.1 五张核心表的关系设计回到这个项目的本质用户在一段时间内记录收入和支出需要随时知道某个分类花了多少、某个账户还剩多少、有没有超出预算。要实现这些最少需要五张核心表用户表、账户表、分类表、流水表、预算表。先看这五张表之间的关系。用户表和账户表是一对多关系一个用户可以有多个账户比如现金、银行卡、支付宝、微信钱包。分类表本身支持父子层级比如“餐饮”是一级分类下面可以有“外卖”“聚餐”“零食”这些二级分类。流水表是整个系统最核心的表每一行代表一笔真实的收入和支出它同时关联用户、账户、分类确保每一笔钱都能追溯到时间和来源。预算表则是按年、月、分类来设置额度用来支撑超支提醒。实际开发里我还加了一张可选的账单表用来放房租、话费这类周期固定支出这样前端可以做一个待办账单提醒。如果时间紧张这张表可以不纳入主流程但放在论文的功能结构图里会显得系统设计更完整。这个设计的好处在于职责清晰——每张表只负责一类数据查询的时候通过外键关联起来而不是把所有信息堆在一张大表里。这也是答辩时评委最看重的一个点你有没有真正理解为什么要拆表。2.2 关键字段设计金额精度、日期与状态位表与表之间的关系定了字段级别的设计才是真正的分水岭。这里有几个细节如果处理不当后期会非常痛苦。第一是金额字段。绝对不要用Python的float来存金额。浮点数存在二进制精度问题0.1加0.2算出来是0.30000000000000004短期看差别不大但累计一年流水之后总额对不上账就是大问题。更好的做法是存整数分比如12.34元存成1234要么用SQLite的DECIMAL(10,2)字段配合Python的Decimal类型。我在项目里用的是Decimal类型存字符串的方案——在Python代码中所有金额计算都用Decimal数据库字段声明为NUMERIC这样既避免浮点误差又能应对大于万元的金额。第二是日期字段。很多新手喜欢把日期存成字符串便于显示但这样会导致按月份汇总这类查询非常低效且容易出错。我建议用统一的ISO格式也就是YYYY-MM-DD或完整时间戳这样SQLite内置的strftime、date函数可以直接做区间过滤和按月分组省去大量手写解析逻辑。第三是状态位。流水表里加一个是否删除或是否作废的布尔字段比真正执行DELETE更稳妥。原因很简单如果误删了一笔账用户可以恢复即便不恢复审计的时候也能看到历史痕迹。记账软件最重要的可信度就来自账不能凭空消失。2.3 一份可直接用的建表SQL下面给出一份我在项目中实际使用的核心建表SQL可以直接在SQLite客户端里执行也可以放到项目里通过sqlite3模块初始化数据库。CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at TEXT DEFAULT (datetime(now, localtime)) ); CREATE TABLE accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, type TEXT NOT NULL DEFAULT cash, balance NUMERIC DEFAULT 0, created_at TEXT DEFAULT (datetime(now, localtime)) ); CREATE TABLE categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, parent_id INTEGER REFERENCES categories(id) ON DELETE CASCADE, name TEXT NOT NULL, category_type TEXT NOT NULL CHECK (category_type IN (expense, income)) ); CREATE TABLE transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, account_id INTEGER NOT NULL REFERENCES accounts(id), category_id INTEGER NOT NULL REFERENCES categories(id), amount NUMERIC NOT NULL, trans_type TEXT NOT NULL CHECK (trans_type IN (expense, income)), note TEXT, trans_date TEXT NOT NULL, is_deleted INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime(now, localtime)) ); CREATE TABLE budgets ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, category_id INTEGER NOT NULL REFERENCES categories(id), year INTEGER NOT NULL, month INTEGER NOT NULL, budget_amount NUMERIC NOT NULL, UNIQUE(user_id, category_id, year, month) ); CREATE TABLE bills ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, amount NUMERIC NOT NULL, due_day INTEGER NOT NULL, is_paid INTEGER DEFAULT 0, note TEXT );建表时还应该给 transactions 表的 user_id、trans_date 字段创建索引这一步容易被忽略但数据量到几千条之后索引对查询速度的提升是非常明显的。CREATE INDEX idx_trans_user_date ON transactions(user_id, trans_date); CREATE INDEX idx_trans_category ON transactions(category_id);3. 核心功能模块的实现顺序与踩坑实录3.1 记账主界面录入效率决定项目生死记账软件有一个反直觉的规律再美观的界面如果录一笔账需要点五六下用户三天就会弃用。作为毕设项目你不一定要做出商业产品的体验但主界面的设计逻辑必须体现出如何减少用户操作次数这个思考。这个思考在答辩时是非常加分的点。我的做法是把主窗口拆成三个区域左侧是功能导航中间是流水表格右侧是快速记账面板。快速记账面板做成一个独立的表单用户选择收支类型、金额、分类、账户点保存即写入数据库主表格自动刷新。这里有几个细节值得展开。金额输入框要默认绑定回车键保存这样用户连续录账时不用鼠标点按钮。收支类型用两个大按钮切换而不是下拉框减少一次点击。分类选择用一个二级联动下拉框选了餐饮之后第二级自动只显示外卖、聚餐这些子分类。账户这个字段必须给默认值——大多数人主要用一个账户默认选中上次使用的账户可以明显减少操作。在事件绑定的逻辑上PySide6里的写法非常直观核心就一句self.save_btn.clicked.connect(self.save_transaction)。但要注意保存之后应该在内存里清空表单状态而不是关闭面板这样连续记账时窗口不会来回弹用户体验会好很多。3.2 分类管理与树形结构分类模块其实比看起来要复杂。记账时用户面临的选择场景多今天午饭花了25记到餐饮-外卖下个月要交房租记到居住-房租。如果分类是平铺的列表会很长找起来费劲如果完全不分类统计报表的粒度又不够。所以分类一定要做两级树形结构。在界面上用一个QTreeWidget展示分类一级节点是餐饮交通居住娱乐这类大分类子节点是具体明细。分类类型要区分支出和收入比如支出下有餐饮、交通收入下有工资、奖金、理财两类不能混用否则统计口径就乱了。这个模块的坑在于删除分类如果某个分类下已经有关联的流水直接删除会导致流水表的category_id指向不存在的数据统计时会出现关联查询丢数据。我的方案是删除前先查流水表中是否存在该分类的记录存在就提示该分类下已有N笔记录不能被删除请先转移或删除流水。这样从源头上保证了数据完整性。3.3 统计报表Matplotlib在Qt里的嵌入要点记账本如果没有可视化报表价值会大打折扣。我在项目里做了三个图分类占比饼图、月度收支柱状图、近半年收支趋势折线图这几乎覆盖了评委最爱看的统计角度。Matplotlib嵌入PySide6关键步骤其实就两步先创建FigureCanvasQTAgg对象再把画布塞进布局。我踩过的最大一个坑是中文字体乱码。Matplotlib默认字体里没有中文字符图表的标题、图例直接显示为一个个方框。解决方式是在绘图前锁定字体核心代码是import matplotlib from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure matplotlib.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, WenQuanYi Micro Hei] matplotlib.rcParams[axes.unicode_minus] False class StatsChart(FigureCanvas): def __init__(self, parentNone): self.fig Figure(figsize(6, 4), dpi100) super().__init__(self.fig) self.setParent(parent) self.ax self.fig.add_subplot(111)一个容易忽略的细节是每次刷新图表前要clear一下坐标系否则多次切换日期范围后新旧图形会叠加在一起。实际上不会每次叠加到一起但如果用self.ax.plot()往同一个坐标系上持续追加第二次查询的数据线会叠在第一次的数据线上让人误以为数据错了或代码有bug。养成在绘图开始调一次self.ax.clear()的好习惯这个问题就不会出现。3.4 预算提醒与超支预警的判断逻辑预算模块的实现逻辑不复杂但合理很关键。每个月设置预算后系统要能算出当前已支出多少、剩余多少、超支多少。为了避免一条SQL写得又臭又长我把判断逻辑拆成了两步。第一步从预算表读出当前月份所有分类的预算额度第二步按分类汇总当月实际支出然后两者在Python内存中做对齐。这样代码更可读也方便后续扩展当超支比例超过80%时提前预警这类逻辑。超支判断我用一个简单的比例阈值。当实际支出超过预算的80%时界面上显示橙色提醒超过100%时显示红色并置顶。为了复现这个判断逻辑还专门在测试阶段造了一批超过预算的数据让演示时界面上直接有醒目效果答辩效果比冷冰冰的纯表格好很多。4. 从能跑到源码能打连表查询、数据备份与打包分发4.1 月度汇总SQL的写法与性能优化统计功能是记账本最核心的能力但也是新手最容易写崩的部分。比如查询本月每个分类的总支出很多人的第一反应是读出全部流水在Python里循环累加。数据量小的时候没问题但一旦有几千条数据这种写法既不优雅也不高效。正确做法是尽量把聚合计算下推到数据库里。这里给出一个我项目里实际用的月度分类汇总SQL注释也一并附上SELECT c.name AS category_name, COUNT(t.id) AS transaction_count, SUM(t.amount) AS total_amount FROM transactions t JOIN categories c ON t.category_id c.id WHERE t.user_id :user_id AND t.trans_type expense AND t.is_deleted 0 AND t.trans_date :month_start AND t.trans_date :month_end GROUP BY c.id ORDER BY total_amount DESC;这条SQL用了参数绑定而不是拼接字符串这是很多毕设代码里会出现的扣分点。SQL注入在单机应用里看起来无所谓但答辩时如果被问到如果用户在备注里输入; DROP TABLE transactions;--这种内容会怎么样一句我做了参数绑定就是标准的加分回答。月度统计和年度统计的表结构一样只是日期区间的起点终点不同。我封装了一个get_report_data(year, month)函数内部通过构造start_date和end_date来复用同一条SQL这样代码重复度很低维护起来非常方便。4.2 数据备份与恢复数据备份这个点很多同学做毕设时压根不会想到但评委喜欢问。记账本本质上是一个数据资产型应用用户最怕的就是重装系统或误删数据库导致账目全部丢失。因此项目里必须提供一个导出数据功能最好再有两个出口导出CSV备份和导出SQLite数据库文件副本。CSV导出适合给用户看用Python内置的csv模块就能搞定需要注意编码指定为utf-8-sig否则用Excel打开时中文会乱码。数据库文件备份更简单就是用shutil.copy2把当前db文件复制一份到用户选择的位置。恢复就更直接了——把备份文件拷回数据目录重启应用即可。有一个细节必须处理备份前需要确认当前没有未提交的写操作。在SQLite默认的autocommit模式下一般不会有这个问题但如果你代码里使用了BEGIN和COMMIT就要小心备份文件可能是中间状态。更稳妥的做法是使用SQLite的在线备份APIPython的sqlite3模块直接暴露了conn.backup()方法几行代码就能完成安全热备份这绝对是加分项。import sqlite3 def backup_database(src_path: str, dst_path: str) - None: src_conn sqlite3.connect(src_path) dst_conn sqlite3.connect(dst_path) src_conn.backup(dst_conn) dst_conn.close() src_conn.close()4.3 PyInstaller打包GUI应用的坑项目完成后最终交付形态除了源码最好还有一个直接能双击运行的exe。PyInstaller是最常见的打包工具但打包PySide6 Matplotlib的项目有几个必经的坑我一一说明。第一个坑是打包体积和缺失依赖。PySide6本身非常大打包后exe动辄两三百兆这是正常的不用焦虑。真正麻烦的是Matplotlib需要运行时动态加载后端PyInstaller的静态分析经常抓不全。解决办法是在spec文件里明确加入Matplotlib的data文件或者运行打包命令时带上--collect-data matplotlib。另外一个更省事的办法是--collect-all把整个包的数据和二进制都收进来代价只是最终体积更大一些适合毕设这种不纠结文件大小的场景。第二个坑是路径问题。打包后程序运行时有两个路径一个是源码目录一个是临时解压目录。如果用相对路径读取数据库文件在打包后会定位到临时目录导致用户关闭程序后数据丢失。正确做法是始终基于用户目录或exe所在目录计算数据文件路径并在程序第一次启动时自动创建数据库文件核心代码如下import sys from pathlib import Path def get_project_root() - Path: if getattr(sys, frozen, False): return Path(sys.executable).parent return Path(__file__).parent def get_db_path() - Path: root get_project_root() db_path root / data / ledger.db db_path.parent.mkdir(parentsTrue, exist_okTrue) return db_path第三个坑是图标和窗口标题。打包时如果不指定--iconexe就是默认的PyInstaller图标在答辩演示时比较掉价。窗口标题、程序说明这些细节也应当在打包前处理好别让评委看到标题栏还是untitled。5. 答辩演示与论文写作的实战经验5.1 演示数据的准备代码跑通了不代表答辩一定顺利我见过太多人在演示环节翻车——不是程序报错而是数据库里空空如也点开统计报表没有任何数据评委根本看不出这个系统能干什么。聪明的做法是在答辩前专门造一批有说服力的演示数据。造数据也有讲究不是为了填数字而填数字。我的做法是构造近半年的模拟流水每个月餐饮支出十几笔、交通七八笔、购物集中在周末、工资固定在每月10号收入一笔、房租每月1号支出一笔。这样统计报表生成后饼图的比例看起来真实自然折线图有明显的周期波动柱状图能在月底看到消费峰值。评委看着这样的数据往往会下意识地问这些数据是怎么来的这时候就可以顺势引出系统的数据导入和批量生成功能反而成了展示亮点。批量生成演示数据的代码我放在了一个独立脚本里用random和datetime模块生成随机流水但金额控制在合理范围比如餐饮单笔10到100元、服饰单笔100到1000元。同时设定随机种子保证每次生成的数据一致这样反复演示时看到的报表效果不会飘忽不定。5.2 论文中的图与表论文和源码是两个维度的东西。源码关注功能论文关注逻辑和规范。记账本项目写论文相对容易但有三张图是必须认真画的。第一张是E-R图实体关系图。画清楚用户、账户、分类、流水、预算这五张表的实体和联系标注主外键。这里最容易犯的错是把表的字段全部堆进去图变得非常拥挤。正确做法是只画表名、主键和关键外键字段表结构细节放到附录或表设计章节。第二张是功能结构图。把系统拆成用户管理、账目管理、分类管理、统计报表、预算管理、数据备份这些模块用树状层级表达。这张图的意义在于它直接决定了论文章节的组织顺序也是答辩开场白里讲系统架构时的视觉辅助。第三张是核心业务流程图。可以画一张用户录入一笔支出到完成月度统计的流向图覆盖从界面表单到入库、再到汇总查询的完整链路。画的时候注意用统一的符号方框代表操作、菱形代表判断规范程度会直接影响老师对工程素养的判断。5.3 答辩高频问题清单根据我多次旁听答辩的经验记账本项目被问到的高频问题基本逃不出下面这几个。我建议在答辩前把每个问题的答案写成讲稿反复练习。为什么选择SQLite而不是MySQL答单机应用、零配置、文件型数据库便于备份迁移满足家庭场景的并发需求。金额为什么不直接用float答浮点存在二进制精度误差金额累计会出错项目使用Decimal保证精确计算。如果用户删除了一个分类账目怎么办答删除前检查关联流水有流水则禁止删除保证引用完整性。预算超支提醒的判断逻辑是怎么实现的答月度预算表和当月分类支出汇总做比对按80%和100%两个阈值分档提示。系统能支持多少用户同时使用答设计上通过用户表支持多用户但默认定位为单机家庭应用如需多用户可平滑迁移到MySQL等网络数据库。这些问题的答案前提都是你亲手完成了这个项目。如果是完全靠着别人的源码改几个文本框就交差的遇到为什么这个表要加这个索引这类稍微深入的问题就会露馅。最后再说两句个人体会。我在带过的毕业项目里见过太多把源码跑通就当完事的案例但真正拿到高分的作品往往是在细节上下了功夫的录入界面少点了两下鼠标、删除操作多了一次确认、打包之后数据文件不会丢。这些细节单个拿出来都不值一提但合在一起就是一个项目从能用到好用的距离。如果你正在做这个题目我的建议是先花一个晚上把数据表结构定清楚再花三天把增删改查跑通剩下的时间全部用来打磨统计报表和数据展示最后留两天专门准备演示数据和答辩问题。这个节奏走下来你会发现自己交出的不只是一份毕设源码而是一个自己真正从零搭建出来的作品。另外分享一个我实测好用的小技巧在开发阶段可以给分类表预先灌入一套完整的家庭常用分类餐饮、交通、居住、购物、医疗、教育、娱乐、人情、其他收入分类下再放工资、奖金、理财等这样每次测试功能时不需要反复手工建分类界面也始终是饱满的。别小看这点细节它对开发效率的提升远比想象中大。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →