admin管理员组

文章数量:1438907

Python实用开发项目案例03

设置星标 了解更多资讯

在Python学习之旅中,实践项目是巩固知识与提升技能的最佳途径。

今天我想与大家分享另一个实用的Python项目 —— 个人财务记账本应用。这个项目结合了数据库操作、数据分析和图形界面编程。

项目概述

  • 支持添加、删除和查看个人收支记录
  • 提供按年月筛选功能,灵活查看特定时期的财务状况
  • 内置数据统计分析,自动计算总收入、总支出和结余
  • 可视化展示各类别收支情况,直观了解资金流向
  • 支持生成Excel财务报表,便于长期财务规划

技术要点解析

SQLite、Tkinter与数据分析库

SQLite是一款轻量级数据库,非常适合个人应用使用。它无需独立服务器进程,可直接将整个数据库存储在单个文件中,便于部署和携带。

Tkinter作为Python标准GUI库,提供了创建桌面应用界面的简便方法,无需额外安装即可使用。

PandasMatplotlib则分别提供了强大的数据处理和可视化能力,是数据分析的理想工具组合。

面向对象编程设计

该项目采用面向对象方式构建,通过创建FinanceTracker类封装所有功能:

  1. 数据库管理:初始化和维护SQLite数据库,处理数据存储和查询
  2. 界面构建:创建直观的用户交互界面,包括输入表单、记录表格和统计图表
  3. 数据处理:实现记录筛选、统计计算和报表生成等核心功能

功能模块详解

  1. 数据库初始化:创建交易记录表,定义字段类型和关系
  2. 用户界面设计:构建分区域布局,包括记录输入区、数据显示区和统计图表区
  3. 交易记录管理:实现添加和删除记录的功能方法
  4. 数据筛选与统计:按时间段筛选数据并计算关键财务指标
  5. 图表可视化:根据筛选结果生成分类统计图表
  6. 报表导出:将数据导出为Excel格式的详细报表

代码剖析

数据库初始化

数据库初始化方法创建了存储交易记录的表结构:

代码语言:javascript代码运行次数:0运行复制
def init_db(self):
    self.conn = sqlite3.connect('finance.db')
    self.cursor = self.conn.cursor()

    # 创建表
    self.cursor.execute('''
        CREATE TABLE IF NOT EXISTS transactions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            date TEXT NOT NULL,
            type TEXT NOT NULL,
            category TEXT NOT NULL,
            amount REAL NOT NULL,
            description TEXT
        )
    ''')
    self.connmit()

界面构建

界面构建方法采用左右分区设计,左侧为输入区,右侧为数据显示和统计区:

代码语言:javascript代码运行次数:0运行复制
def create_widgets(self):
    # 主框架
    main_frame = ttk.Frame(self.root)
    main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

    # 左侧输入面板
    input_frame = ttk.LabelFrame(main_frame, text="新增记录")
    input_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)

    # 记录类型
    ttk.Label(input_frame, text="类型:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
    self.type_var = tk.StringVar(value="支出")
    ttk.OptionMenu(input_frame, self.type_var, "支出", "收入", "支出").grid(row=0, column=1, padx=5, pady=5,
                                                                            sticky="w")
    # ...其他输入控件...

    # 右侧显示面板
    display_frame = ttk.Frame(main_frame)
    display_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)

    # 交易记录表格
    self.tree = ttk.Treeview(display_frame, columns=("ID", "Date", "Type", "Category", "Amount", "Description"),
                             show="headings")
    # ...表格设置...

    # 统计和图表区域
    stats_frame = ttk.LabelFrame(display_frame, text="月度统计")
    # ...统计和图表组件...

数据处理与统计

数据筛选和统计方法实现了按月份年份查询数据并更新统计信息:

代码语言:javascript代码运行次数:0运行复制
def filter_data(self):
    month = self.month_var.get().replace("月", "")
    year = self.year_var.get()

    try:
        month = int(month)
        year = int(year)
        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"

        self.cursor.execute(
            "SELECT * FROM transactions WHERE date >= ? AND date < ? ORDER BY date DESC",
            (start_date, end_date)
        )

        # 清空表格并加载筛选数据
        # ...代码省略...

        # 更新统计和图表
        self.update_stats()
    except Exception as e:
        messagebox.showerror("错误", f"筛选失败: {str(e)}")

def update_stats(self):
    # 获取当前显示的数据
    data = []
    for item in self.tree.get_children():
        data.append(self.tree.item(item)['values'])

    ifnot data:
        self.stats_text.delete(1.0, tk.END)
        self.stats_text.insert(tk.END, "没有数据")
        return

    df = pd.DataFrame(data, columns=["ID", "Date", "Type", "Category", "Amount", "Description"])

    # 计算统计信息和更新图表
    # ...代码省略...

报表生成

报表生成功能将数据导出为Excel格式,包含详细记录和月度汇总两个工作表:

代码语言:javascript代码运行次数:0运行复制
def generate_report(self):
    try:
        # 获取所有数据
        self.cursor.execute("SELECT * FROM transactions ORDER BY date")
        data = self.cursor.fetchall()

        ifnot data:
            messagebox.showinfo("提示", "没有数据可生成报表")
            return

        df = pd.DataFrame(data, columns=["ID", "Date", "Type", "Category", "Amount", "Description"])

        # 生成月度报表
        df['Date'] = pd.to_datetime(df['Date'])
        df['Month'] = df['Date'].dt.to_period('M')
        monthly = df.groupby(['Month', 'Type'])['Amount'].sum().unstack()

        # 保存报表
        report_filename = f"finance_report_{datetime.now().strftime('%Y%m%d')}.xlsx"
        with pd.ExcelWriter(report_filename) as writer:
            df.to_excel(writer, sheet_name="详细记录", index=False)
            monthly.to_excel(writer, sheet_name="月度汇总")

        messagebox.showinfo("成功", f"报表已生成: {report_filename}")
    except Exception as e:
        messagebox.showerror("错误", f"生成报表失败: {str(e)}")

应用界面展示

完整代码

代码语言:javascript代码运行次数:0运行复制
# -*- coding: utf-8 -*-
import sqlite3
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import ttk, messagebox
from datetime import datetime

class FinanceTracker:
    def __init__(self, root):
        self.root = root
        self.root.title("个人记账本")
        self.root.geometry("900x600")
        
        # 初始化数据库
        self.init_db()
        
        # 创建UI
        self.create_widgets()
        
        # 加载数据
        self.load_data()
    
    def init_db(self):
        self.conn = sqlite3.connect('finance.db')
        self.cursor = self.conn.cursor()
        
        # 创建表
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS transactions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                date TEXT NOT NULL,
                type TEXT NOT NULL,
                category TEXT NOT NULL,
                amount REAL NOT NULL,
                description TEXT
            )
        ''')
        self.connmit()
    
    def create_widgets(self):
        # 主框架
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 左侧输入面板
        input_frame = ttk.LabelFrame(main_frame, text="新增记录")
        input_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
        
        # 记录类型
        ttk.Label(input_frame, text="类型:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
        self.type_var = tk.StringVar(value="支出")
        ttk.OptionMenu(input_frame, self.type_var, "支出", "收入", "支出").grid(row=0, column=1, padx=5, pady=5, sticky="w")
        
        # 日期
        ttk.Label(input_frame, text="日期:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
        self.date_entry = ttk.Entry(input_frame)
        self.date_entry.grid(row=1, column=1, padx=5, pady=5)
        self.date_entry.insert(0, datetime.now().strftime("%Y-%m-%d"))
        
        # 类别
        ttk.Label(input_frame, text="类别:").grid(row=2, column=0, padx=5, pady=5, sticky="e")
        self.category_entry = ttk.Entry(input_frame)
        self.category_entry.grid(row=2, column=1, padx=5, pady=5)
        
        # 金额
        ttk.Label(input_frame, text="金额:").grid(row=3, column=0, padx=5, pady=5, sticky="e")
        self.amount_entry = ttk.Entry(input_frame)
        self.amount_entry.grid(row=3, column=1, padx=5, pady=5)
        
        # 描述
        ttk.Label(input_frame, text="描述:").grid(row=4, column=0, padx=5, pady=5, sticky="e")
        self.desc_entry = ttk.Entry(input_frame)
        self.desc_entry.grid(row=4, column=1, padx=5, pady=5)
        
        # 按钮
        ttk.Button(input_frame, text="添加记录", command=self.add_transaction).grid(row=5, column=0, columnspan=2, pady=10)
        ttk.Button(input_frame, text="删除记录", command=self.delete_transaction).grid(row=6, column=0, columnspan=2, pady=5)
        
        # 右侧显示面板
        display_frame = ttk.Frame(main_frame)
        display_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 交易记录表格
        self.tree = ttk.Treeview(display_frame, columns=("ID", "Date", "Type", "Category", "Amount", "Description"), show="headings")
        self.tree.heading("ID", text="ID")
        self.tree.heading("Date", text="日期")
        self.tree.heading("Type", text="类型")
        self.tree.heading("Category", text="类别")
        self.tree.heading("Amount", text="金额")
        self.tree.heading("Description", text="描述")
        self.tree.column("ID", width=40)
        self.tree.column("Date", width=80)
        self.tree.column("Type", width=60)
        self.tree.column("Category", width=80)
        self.tree.column("Amount", width=80)
        self.tree.pack(fill=tk.BOTH, expand=True)
        
        # 统计和图表
        stats_frame = ttk.LabelFrame(display_frame, text="月度统计")
        stats_frame.pack(fill=tk.X, pady=5)
        
        self.stats_text = tk.Text(stats_frame, height=5)
        self.stats_text.pack(fill=tk.X)
        
        self.figure = plt.Figure(figsize=(5, 3), dpi=100)
        self.canvas = FigureCanvasTkAgg(self.figure, master=display_frame)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        
        # 筛选控制
        filter_frame = ttk.Frame(display_frame)
        filter_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(filter_frame, text="月份:").pack(side=tk.LEFT, padx=5)
        self.month_var = tk.StringVar()
        months = [f"{i}月"for i in range(1, 13)]
        self.month_menu = ttk.OptionMenu(filter_frame, self.month_var, *months)
        self.month_menu.pack(side=tk.LEFT, padx=5)
        
        ttk.Label(filter_frame, text="年份:").pack(side=tk.LEFT, padx=5)
        self.year_var = tk.StringVar(value=str(datetime.now().year))
        self.year_entry = ttk.Entry(filter_frame, width=6, textvariable=self.year_var)
        self.year_entry.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(filter_frame, text="筛选", command=self.filter_data).pack(side=tk.LEFT, padx=5)
        ttk.Button(filter_frame, text="生成报表", command=self.generate_report).pack(side=tk.LEFT, padx=5)
    
    def add_transaction(self):
        try:
            date = self.date_entry.get()
            trans_type = self.type_var.get()
            category = self.category_entry.get()
            amount = float(self.amount_entry.get())
            description = self.desc_entry.get()
            
            self.cursor.execute(
                "INSERT INTO transactions (date, type, category, amount, description) VALUES (?, ?, ?, ?, ?)",
                (date, trans_type, category, amount, description)
            )
            self.connmit()
            
            self.load_data()
            self.clear_inputs()
        except Exception as e:
            messagebox.showerror("错误", f"添加记录失败: {str(e)}")
    
    def delete_transaction(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请先选择要删除的记录")
            return
        
        trans_id = self.tree.item(selected[0])['values'][0]
        try:
            self.cursor.execute("DELETE FROM transactions WHERE id=?", (trans_id,))
            self.connmit()
            self.load_data()
        except Exception as e:
            messagebox.showerror("错误", f"删除记录失败: {str(e)}")
    
    def load_data(self):
        # 清空表格
        for row in self.tree.get_children():
            self.tree.delete(row)
        
        # 加载数据
        self.cursor.execute("SELECT * FROM transactions ORDER BY date DESC")
        for row in self.cursor.fetchall():
            self.tree.insert("", tk.END, values=row)
        
        # 更新统计和图表
        self.update_stats()
    
    def filter_data(self):
        month = self.month_var.get().replace("月", "")
        year = self.year_var.get()
        
        try:
            month = int(month)
            year = int(year)
            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"
            
            self.cursor.execute(
                "SELECT * FROM transactions WHERE date >= ? AND date < ? ORDER BY date DESC",
                (start_date, end_date)
            )
            
            # 清空表格
            for row in self.tree.get_children():
                self.tree.delete(row)
            
            # 加载筛选数据
            for row in self.cursor.fetchall():
                self.tree.insert("", tk.END, values=row)
            
            # 更新统计和图表
            self.update_stats()
        except Exception as e:
            messagebox.showerror("错误", f"筛选失败: {str(e)}")
    
    def update_stats(self):
        # 获取当前显示的数据
        data = []
        for item in self.tree.get_children():
            data.append(self.tree.item(item)['values'])
        
        if not data:
            self.stats_text.delete(1.0, tk.END)
            self.stats_text.insert(tk.END, "没有数据")
            return
        
        df = pd.DataFrame(data, columns=["ID", "Date", "Type", "Category", "Amount", "Description"])
        
        # 计算统计信息
        income = df[df['Type'] == '收入']['Amount'].sum()
        expense = df[df['Type'] == '支出']['Amount'].sum()
        balance = income - expense
        
        # 显示统计信息
        self.stats_text.delete(1.0, tk.END)
        self.stats_text.insert(tk.END, f"总收入: {income:.2f}\n")
        self.stats_text.insert(tk.END, f"总支出: {expense:.2f}\n")
        self.stats_text.insert(tk.END, f"结余: {balance:.2f}\n")
        
        # 更新图表
        self.figure.clear()
        ax = self.figure.add_subplot(111)
        
        # 按类别分组
        if not df.empty:
            grouped = df.groupby(['Type', 'Category'])['Amount'].sum().unstack()
            grouped.plot(kind='bar', stacked=True, ax=ax)
            ax.set_title("收支分类统计")
            ax.set_ylabel("金额")
            ax.legend(title="类别")
            self.canvas.draw()
    
    def generate_report(self):
        try:
            # 获取所有数据
            self.cursor.execute("SELECT * FROM transactions ORDER BY date")
            data = self.cursor.fetchall()
            
            if not data:
                messagebox.showinfo("提示", "没有数据可生成报表")
                return
            
            df = pd.DataFrame(data, columns=["ID", "Date", "Type", "Category", "Amount", "Description"])
            
            # 生成月度报表
            df['Date'] = pd.to_datetime(df['Date'])
            df['Month'] = df['Date'].dt.to_period('M')
            monthly = df.groupby(['Month', 'Type'])['Amount'].sum().unstack()
            
            # 保存报表
            report_filename = f"finance_report_{datetime.now().strftime('%Y%m%d')}.xlsx"
            with pd.ExcelWriter(report_filename) as writer:
                df.to_excel(writer, sheet_name="详细记录", index=False)
                monthly.to_excel(writer, sheet_name="月度汇总")
            
            messagebox.showinfo("成功", f"报表已生成: {report_filename}")
        except Exception as e:
            messagebox.showerror("错误", f"生成报表失败: {str(e)}")
    
    def clear_inputs(self):
        self.date_entry.delete(0, tk.END)
        self.date_entry.insert(0, datetime.now().strftime("%Y-%m-%d"))
        self.category_entry.delete(0, tk.END)
        self.amount_entry.delete(0, tk.END)
        self.desc_entry.delete(0, tk.END)
    
    def __del__(self):
        self.conn.close()

if __name__ == "__main__":
    root = tk.Tk()
    app = FinanceTracker(root)
    root.mainloop()

总结

个人财务记账本项目虽然看似简单,却涵盖了SQLite数据库操作、Tkinter界面设计、Pandas数据分析和Matplotlib可视化等多种Python技术。通过开发这个应用,不仅能解决个人财务管理的实际问题,还能全面提升Python开发技能。这个项目展示了如何将多个技术模块无缝整合,构建一个实用的桌面应用程序。

在下一篇文章中,我们将继续探索更多Python实用小项目,不断丰富我们的编程技能库。敬请期待!

如果你觉得文章还不错,请大家 点赞、分享、留言 下,因为这将是我持续输出更多优质文章的最强动力

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-04-17,如有侵权请联系 cloudcommunity@tencent 删除数据统计pythonself开发

本文标签: Python实用开发项目案例03