新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于Python与SQLite的个人财务管理系统开发实战

发布时间:2026/10/2 9:09:21来源:尧图网络
基于Python与SQLite的个人财务管理系统开发实战
1. 项目设计与技术选型1.1 需求分析记账工具到底需要解决什么问题记账这件事大多数人坚持不了几天不是因为懒而是因为“记账”和“看账”被割裂了。随手在手机备忘录里记了几笔月底想看开销结构还得自己打开Excel抄一遍分类再拉透视表繁琐到劝退。我做这套基于Python的个人财务管理系统时第一原则就是“录入之后无需再做任何手工整理”所有统计、汇总、可视化全部自动完成。功能清单我一开始列得并不长只挑最核心的部分收支录入字段控制在最少分类、金额、日期、备注分类管理收入和支出分类分开维护月度汇总自动算总收入、总支出、结余可视化月度收支趋势折线图、分类占比饼图数据导出CSV备份像预算预警、多人账户、信用卡账单导入这些都属于第二版功能第一版不做。原因很简单功能越多代码调试成本和维护成本越高对一个练手项目来说完成闭环比堆功能重要得多。如果你刚学完Python基础想找一个既能练手、又能真正用起来的项目这套系统的复杂度非常合适——有数据库操作、有业务逻辑、有界面交互但每一块都不至于把人劝退。1.2 技术选型为什么是Python、SQLite、Tkinter先说语言。Python在这种个人工具类项目里的优势是压倒性的脚本型语言迭代快核心逻辑几百行就能写完生态里有matplotlib、pandas这些现成库统计和绘图完全不用自己造轮子。数据库连接也有标准库sqlite3不需要额外装驱动。数据库层面我选的是SQLite而不是MySQL核心原因是“零配置”。SQLite就是一个单文件数据库不需要安装服务、不需要管用户名密码连接即用。对个人桌面应用来说性能和容量完全够用几万条收支记录查询毫秒级返回。有人担心SQLite太小众其实它是Python标准库内置的而且如果将来要放到服务器上多人共用把连接层从SQLite切到MySQL改动成本也不大因为SQL语句本身是通用的业务逻辑不用动。界面框架我用了Tkinter。它是Python标准库自带的GUI工具包最大的好处是不用额外安装、跨平台表现一致对表单输入、列表展示、按钮触发这类简单场景足够。如果你嫌它外观朴素可以换成PyQt或PySide但那些包体积大、学习曲线陡第一版没必要上。也有人喜欢做成本地Web版用Flask包一个网页当界面那是另一种思路以后可以单独展开。我的建议是先选定一套能跑通全流程的方案界面好看不好看不影响核心逻辑的价值。层面方案优势劣势语言Python生态丰富、开发迭代快运行性能弱于C/Java数据库SQLite单文件、零配置、内置支持不适合高并发场景界面Tkinter标准库自带、部署简单美观度一般1.3 项目源码结构规划源码的组织方式直接影响后续维护心情。我见过很多课设项目把所有代码塞进一个几百行的main.py里界面逻辑、数据库代码、业务计算混在一起改一个字段要翻半天的代码。这套系统我按职责拆成了几个文件personal-finance/ ├── main.py # 程序入口与界面布局 ├── db.py # 数据库连接与初始化 ├── category_dao.py # 分类相关的增删改查 ├── transaction_dao.py # 收支记录相关的增删改查 ├── report.py # 统计汇总与图表绘制 ├── docs/ │ ├── requirements.md # 需求说明 │ ├── design.md # 设计文档 │ └── user_manual.md # 使用手册 └── finance.db # SQLite数据库文件数据访问单独放一层界面代码只管布局和交互数据库代码只管SQL中间通过函数调用衔接。这样做的好处是改界面不会碰到数据逻辑加统计功能不用动现有页面。这个分层习惯即使将来换Web框架也一样适用算是我在项目里最想强调的一点。2. 核心功能拆解与数据库设计2.1 功能模块的完整闭环从记账到报表整套系统的流程大概是录一笔账 → 写入数据库 → 月度汇总自动更新 → 可视化图表刷新 → 一键导出CSV备份。闭环的意思是每个功能都不是孤立的录入的每一笔数据最终都会汇总到统计和图表里不需要用户手动去更新任何中间结果。录入模块是入口表单包含日期、分类、金额、类型和备注点击保存后做数据校验再入库。分类管理负责维护收入和支出两个维度的分类列表系统里会预置一批常用分类用户也可以随时加。统计模块按月份聚合数据算出收入合计、支出合计和结余同时按分类聚合支出得到每个分类的花费占比。可视化模块把统计结果画成折线图和饼图月度趋势看收入支出的变化饼图看各类支出占比。CSV导出则是把查询结果写成本地文件用于备份和用Excel做进一步分析。这里有一个设计上的经验业务逻辑不要写在界面的事件回调里。比如“点击统计按钮”这件事正确做法是按钮回调只负责调用report.py里的函数拿到结果再刷新界面。否则统计代码和界面代码耦合后很难单独测试也不方便复用。2.2 数据库表结构设计数据库设计是这套系统的地基。我建了三张表categories分类表、transactions收支流水表、budgets预算表建表SQL如下CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, type TEXT NOT NULL CHECK(type IN (income, expense)), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, category_id INTEGER NOT NULL, type TEXT NOT NULL CHECK(type IN (income, expense)), amount REAL NOT NULL, note TEXT, trans_date DATETIME NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (category_id) REFERENCES categories(id) ); CREATE TABLE IF NOT EXISTS budgets ( id INTEGER PRIMARY KEY AUTOINCREMENT, category_id INTEGER NOT NULL, monthly_limit REAL NOT NULL, month TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(category_id, month), FOREIGN KEY (category_id) REFERENCES categories(id) ); CREATE INDEX IF NOT EXISTS idx_trans_date ON transactions(trans_date); CREATE INDEX IF NOT EXISTS idx_trans_category ON transactions(category_id);几个关键选择的理由日期字段用DATETIME类型不用字符串。原因是数据库层面的日期排序、范围查询比如查某个月的数据都依赖类型本身的比较规则字符串月份格式虽然也能排序但一旦格式不统一就会出问题。type字段用CHECK约束限定只能是income或expense从数据库层面挡住非法数据。应用层再怎么校验也不如数据库兜底来得稳。trans_date和category_id建了索引因为月度统计和分类聚合是最频繁的查询索引能把扫描范围缩小一个量级。budgets表里加了UNIQUE(category_id, month)意思是同一个分类同一个月份只能有一条预算记录重复写入直接冲突省得在代码里判断“是否存在”。2.3 数据库初始化与预置数据首次运行系统时数据库文件还不存在这时需要执行初始化脚本。初始化做了三件事连接数据库、执行建表SQL、写入预置分类数据。预置分类不能太粗也不能太细我按实际记账体验整理了一套支出分类餐饮、交通、居住、购物、娱乐、医疗、教育、其他收入分类工资、奖金、理财收益、其他初始化函数的写法import sqlite3 DB_PATH finance.db def init_db(): with sqlite3.connect(DB_PATH) as conn: conn.executescript(DDL_SQL) categories [ (餐饮, expense), (交通, expense), (居住, expense), (购物, expense), (娱乐, expense), (医疗, expense), (教育, expense), (其他支出, expense), (工资, income), (奖金, income), (理财收益, income), (其他收入, income), ] for name, ctype in categories: conn.execute( INSERT OR IGNORE INTO categories(name, type) VALUES(?, ?), (name, ctype) )用INSERT OR IGNORE而不是普通INSERT是为了让初始化脚本可重复执行多跑几次也不会报重复数据错误。using with sqlite3.connect 这种写法事务会自动提交遇到异常自动回滚简单可靠。数据库初始化脚本放到db.py里入口main.py启动时调用一次init_db()即可。3. 实操过程与核心代码实现3.1 环境准备从Python安装到依赖配置拿到源码后想跑起来第一步是准备Python环境。推荐用3.8以上版本我开发时用的是Python 3.10。安装过程其实没啥技术含量重点是用虚拟环境隔离依赖避免和系统Python环境互相污染。python -m venv venv source venv/bin/activate # Windows下执行 venv\Scripts\activate pip install matplotlib pandas如果你在国内的环境pip下载速度可能很慢可以临时指定国内镜像源pip install matplotlib pandas -i https://pypi.tuna.tsinghua.edu.cn/simplematplotlib用来画统计图表pandas是数据处理工具这两个是除了标准库之外仅有的外部依赖。Tkinter是标准库自带的不用单独装但在部分Linux发行版上需要手动安装python3-tk包否则import tkinter会报错。Windows和macOS的官方Python安装包一般都自带。依赖装好后执行python db.py初始化数据库然后python main.py启动界面。整个项目不依赖任何外部服务本机就能跑这也是个人财务管理系统比服务端项目更适合练手的原因。3.2 核心代码数据库增删改查的落地写法数据访问层的代码是整个系统的核心。我做这套系统时最重要的一条编码经验就是一律用参数化查询不要用字符串拼接SQL。虽然本地单机应用被注入攻击的风险不大但字符串拼接SQL还有一个隐性坑——金额、日期这类字段值里如果带了引号或特殊字符拼接出来的SQL就会语法错误程序直接崩溃。录入一笔收支记录的函数def add_transaction(category_id, trans_type, amount, note, trans_date): with sqlite3.connect(DB_PATH) as conn: conn.execute( INSERT INTO transactions(category_id, type, amount, note, trans_date) VALUES(?, ?, ?, ?, ?), (category_id, trans_type, amount, note, trans_date) )查询某个月份的汇总数据def monthly_summary(year, month): start_date f{year}-{month:02d}-01 if month 12: end_date f{year 1}-01-01 else: end_date f{year}-{month 1:02d}-01 with sqlite3.connect(DB_PATH) as conn: row conn.execute( SELECT COALESCE(SUM(CASE WHEN typeincome THEN amount END), 0) AS income, COALESCE(SUM(CASE WHEN typeexpense THEN amount END), 0) AS expense FROM transactions WHERE trans_date ? AND trans_date ?, (start_date, end_date) ).fetchone() return {income: row[0], expense: row[1], balance: row[0] - row[1]}这里有一个容易忽略的点查询月份区间用的是左闭右开即 start_date和 end_date。这样处理的原因是下个月1日的0点整点不好算但 next_month_01能天然把本月最后一天包含进来逻辑最干净。COALESCE包裹SUM是为了防止没有记录时返回NULL导致后续计算报错。编辑和删除记录同理删除除了删transactions表里的记录还要考虑是否有统计缓存要重建——这个项目里没有缓存所以直接删即可。增删改查合起来就是数据层最基本的四个函数add、update、delete、query业务层调用这四个函数界面也调用这四个函数整体结构非常清晰。3.3 统计与可视化用matplotlib把账目画出来统计是个人财务管理系统最出效果的部分。前面monthly_summary函数已经算好了数据可视化就是把结构化的数字变成图表。我用了matplotlib先看月度收支趋势的折线图def draw_trend(data): import matplotlib.pyplot as plt months [d[month] for d in data] incomes [d[income] for d in data] expenses [d[expense] for d in data] plt.figure(figsize(9, 5)) plt.plot(months, incomes, markero, label收入) plt.plot(months, expenses, markero, label支出) plt.title(月度收支趋势) plt.xlabel(月份) plt.ylabel(金额) plt.legend() plt.xticks(rotation45) plt.tight_layout() plt.savefig(trend.png)很多人在这一步踩坑matplotlib默认字体不支持中文plt.title(月度收支趋势)执行后图上全是方框。解决办法是在绘图前设置字体plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False第二行axes.unicode_minus设置的负号显示问题。如果你的系统装了中文字体设置完后中文就能正常渲染。Linux服务器上一般没有微软雅黑和黑体可以用plt.rcParams[font.sans-serif] [WenQuanYi Zen Hei]或者先fc-list :langzh查看可用字体。分类占比饼图的代码更简单核心是算出各类支出占比后传入plt.pie强调一块是否超预算可以加参数explode突显。画完图直接保存成PNG文件界面里再用Label加载图片展示这样避免Tkinter和matplotlib的嵌入机制纠缠实现起来最稳。3.4 主程序与界面逻辑界面层我用Tkinter做了个单窗口布局顶部是录入表单中间是条目列表底部是统计按钮和导出按钮。表单用了StringVar绑定输入框内容分类用ttk.Combobox下拉选择金额输入框只允许输入数字和一个小数点通过validate回调实现def validate_amount(text): if text : return True try: float(text) return True except ValueError: return False保存按钮的回调函数负责收集表单数据、调用transaction_dao.add_transaction成功后清空表单并刷新下面的列表。刷新列表其实就是重新查询数据库再次显示在Treeview控件里。这个“表单录入→保存→刷新列表”的模式是绝大多数桌面录入工具的基本交互范式。界面代码里需要特别注意的一点是不要在UI线程里做耗时操作。本项目的SQLite查询都是毫秒级不会卡界面但如果以后接了远程数据库或大数据量报表就要考虑用线程把查询放到后台否则点击按钮后整个窗口会无响应几秒钟。4. 常见问题与排查技巧实录4.1 SQLite数据库打不开、表不存在跑源码时最常见的报错是sqlite3.OperationalError: no such table: transactions。出现这个报错基本可以断定是没执行初始化或者程序连错了数据库文件。SQLite连接时会自动创建空的.db文件所以如果db.py里的DB_PATH和查询函数里的DB_PATH不一致就会出现一个文件里建了表、另一个文件里查数据的情况。排查思路分三步先确认代码里所有DB_PATH指向同一个路径最好在db.py里定义一个全局常量其他文件统一from db import DB_PATH。运行一次python db.py确认finance.db文件生成且大小不是0字节。用sqllite命令行或数据库工具打开finance.db执行.tables看表是否存在。另外要注意不要把建表SQL写在业务查询函数里反复执行。虽然IF NOT EXISTS能防重复但每次查询前都执行一次DDL是浪费。初始化只做一次就够了。4.2 中文乱码与图表不显示中文中文问题分两种。第一种是控制台和CSV导出的乱码主要是编码问题。写CSV文件时我建议用utf-8-sig编码而不是默认的utf-8with open(data.csv, w, encodingutf-8-sig) as f: ...用utf-8-sig会在文件头写入BOM标记Excel打开后中文不会乱码直接utf-8编码在Windows下的Excel里经常显示成乱码。第二种是matplotlib图表里的中文方框解决办法就是前面提到的rcParams字体设置核心是要确保当前系统里存在对应字体。4.3 金额精度问题用float还是Decimal记账系统的金额计算对精度要求很高。直接用float存在二进制浮点的坑0.1 0.2的结果并不是0.3而是0.30000000000000004。月度汇总里涉及大量金额相加浮点累加会积累误差长期用下来账目对不上。我的处理办法是金额在录入时用Decimal做校验从数据库读取出来后在Python侧统一转成Decimal再计算。SQLite本身把金额存成REAL类型读取后转Decimal会有精度损失风险所以更稳妥的做法是金额字段在数据库里存整数分比如100.50元存成10050展示时再除以100。这样彻底绕开浮点问题。如果你只是个人记账数据量不大用Decimal在应用层计算也能接受但存整数分的方案更干净。4.4 PyInstaller打包成exe的注意事项很多读者拿到源码后想打包成exe发给朋友用。PyInstaller是常用工具但有几个坑pip install pyinstaller pyinstaller -F -w main.py-F参数把所有依赖打进单个exe-w参数表示不显示控制台窗口。打包过程本身不难难的是数据库文件的处理。如果你把finance.db当数据文件塞进exe程序第一次运行会初始化它但由于exe运行时的工作目录和数据库路径不一致用户记账的数据会写到临时目录里下次打开又生成一个新的临时目录造成“数据丢失”的错觉。我的建议是打包时不捆绑数据库文件让程序在exe所在目录的旁边创建finance.db。做法是在代码里根据sys.executable的目录动态计算DB_PATH而不是写死相对路径。说实话打包只是锦上添花个人工具自己用Python跑就行追求稳定优先。最后分享一点个人体会这套系统我自己实际用了三个多月最大的感觉是“自动生成图表”让记账变成了一件有反馈的事。月初录几笔月末打开折线图看一眼这个月的消费趋势和类别占比一目了然比自己在记账软件里翻汇总页直观得多。拿到源码的朋友我建议不要急着改界面先把数据库表结构读懂再用transaction_dao.py里那几个增删改查函数试着手动插入一条假记录然后跑一次月度汇总。这个过程走完整个系统的数据流就通了接下来加功能、改样式都是水到渠成的事。后续如果想继续扩展可以加个预算预警budgets表里已经预留了monthly_limit字段录入支出时顺便查一下当前分类本月累计消费是否超过预算超过就在界面上给个提示。这个功能改动量不大但会让系统从“单纯记账”升级成“主动帮你盯预算”实用价值会高一个台阶。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

ITSK万能驱动26V5:新驱动批量更新与系统封装离线部署实操 2026/10/2 10:32:04

ITSK万能驱动26V5:新驱动批量更新与系统封装离线部署实操

最近在整理测试机房的镜像部署流程时,遇到一个很头疼的问题:新到的这批测试平台,芯片组和网卡驱动怎么都装不顺,系统装完网卡不起,设备管理器里一片黄色感叹号。正好手头有ITSK万能驱动26V5这套新版本驱动包&#xff0…

阅读更多 →
AI Agent+OpenSCAD+3D打印:从代码到实物的参数化智造流水线 2026/10/2 10:31:57

AI Agent+OpenSCAD+3D打印:从代码到实物的参数化智造流水线

最近我把自己的小工作台升级成了一条“代码到实物”的流水线:AI Agent 负责写 OpenSCAD 模型代码,OpenSCAD 负责参数化预览和导出 STL,3D 打印机负责把 STL 变成能拿在手里的零件。整套流程我给它起了个名字叫“AI智造小工坊”,现…

阅读更多 →
LE270-IN-1D3W6-10模组SDK开发实战:从AT命令到OpenCPU 2026/10/2 10:31:57

LE270-IN-1D3W6-10模组SDK开发实战:从AT命令到OpenCPU

1. 项目概述:LE270-IN-1D3W6-10与Fibocom SDK到底在做什么1.1 型号命名背后的信息我第一次拿到LE270-IN-1D3W6-10这块模组的时候,第一反应是先去查它的硬件版本号和SDK支持范围。搞过几年蜂窝模组开发的朋友应该都清楚,型号后缀里往往藏着不少…

阅读更多 →
VBA模板同步实战:基于WorkBuddy的母版-副本自动分发机制 2026/10/2 10:31:51

VBA模板同步实战:基于WorkBuddy的母版-副本自动分发机制

1. 从一堆散装模板到统一母版:我为什么要折腾这套同步机制 手里管着十几套 VBA 模板文档,是我这两年做数据处理和报表自动化最头疼的事。每个项目一套模板,每个模板里塞着不同的宏、不同的表头、不同的数据校验逻辑。刚开始还能靠脑子记&…

阅读更多 →
ARC Welder:Chrome 运行安卓 APK 的应用级封装与兼容实践 2026/10/2 10:31:51

ARC Welder:Chrome 运行安卓 APK 的应用级封装与兼容实践

我最早盯上 ARC Welder,是因为一个特别具体的需求:手上有一堆安卓 APK,想在电脑上快速点开看看长什么样,又不想为了一个几十兆的包去装几个 G 的模拟器。那阵子我的做法很粗暴——装模拟器、等开机、风扇起飞、内存被吃掉一半&…

阅读更多 →
RTX 4090本地大模型部署:从能跑到好用的全栈调优指南 2026/10/2 10:31:51

RTX 4090本地大模型部署:从能跑到好用的全栈调优指南

1. 这不是显卡升级,是本地大模型工作流的彻底重写 我花4400块换了一张RTX 4090,不是为了打游戏,也不是为了渲染视频——而是把原来在云端跑、卡在API调用里、等响应像等快递签收一样的本地大模型推理,硬生生拽回自己桌面上&#x…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉