Python 零基础入门系列(终篇):综合实战项目

🚀 Python 零基础入门系列(终篇):综合实战项目

《代码之钥》------ 个人记账管理系统(Personal Accounting Manager)

📢 系列说明

恭喜你来到本系列的收官之作!在前十三篇中,我们从基础语法到函数,从文件操作到异常处理,你已经掌握了 Python 的"十八般兵器"。

俗话说:"纸上得来终觉浅,绝知此事要躬行。"

本文将带你把前 13 篇学到的所有知识(语法、函数、模块、文件、异常等)融合在一起,从零开始开发一个完整的 "个人记账管理系统"。这不仅是一个项目,更是你迈向 Python 开发者之路的"成人礼"。

📅 更新时间:2026 年 3 月 30 日

🎯 本篇你将学到:项目需求分析、模块化设计、数据持久化、异常处理实战、面向过程的综合应用

⏱️ 预计阅读时间 :60 分钟 | 💻 实践时间:90-120 分钟

📌 前置知识:已完成前十三篇所有内容

✍️ 作者:书到用时方恨少!


🌟 前言:为什么要开发这个项目?

在前十三篇教程中,我们写的代码大多是"碎片化"的。今天,我们要把这些碎片拼成一幅完整的画卷。

记账管理系统是一个经典的 CRUD(增删改查)应用:

  • Create (创建):添加一笔收入或支出。
  • Read (读取):查看所有账单或按条件查询。
  • Update (更新):修改账单信息。
  • Delete (删除):删除错误账单。

通过这个项目,你将学会:

  1. 模块化思维:如何把一个大程序拆分成多个小文件。
  2. 数据持久化 :利用 JSON 模块将内存数据保存到硬盘。
  3. 防御性编程 :利用 try-except 处理用户错误输入。
  4. 工程化结构:如何组织一个 Python 项目的目录。

1. 项目需求分析

1.1 核心功能

  1. 启动加载:程序启动时自动从文件加载历史账单。
  2. 记账功能
    • 录入类型(收入/支出)。
    • 录入金额。
    • 录入分类(如餐饮、交通、工资等)。
    • 录入日期(默认当前日期)。
    • 录入备注。
  3. 查询功能
    • 查看所有账单。
    • 按月份查询。
    • 按分类查询。
  4. 统计功能:显示当前余额、总收入和总支出。
  5. 退出保存:程序退出时自动保存数据。

1.2 数据结构设计

我们需要一个结构来存储每一笔"记录":

复制代码
{
    "id": 1, # 唯一标识
    "type": "支出", # 收入/支出
    "amount": 50.0, # 金额
    "category": "餐饮", # 分类
    "date": "2026-04-01", # 日期
    "note": "午餐" # 备注
}

所有记录将存储在一个列表中,并使用 JSON 格式保存在 accounting.json 文件中。

1.3 技术栈回顾

  • 基础语法:变量、数据类型(字典、列表)。
  • 流程控制if-else 判断,while 循环。
  • 函数:封装重复代码。
  • 模块json 模块处理数据,os 模块检查文件。
  • 异常处理try-except 捕获输入错误(如输入非数字金额)。

2. 项目架构设计

为了代码清晰,我们将项目拆分为 4 个模块:

2.1 目录结构

复制代码
Personal_Accounting/
│
├── data/                  # 数据文件夹
│   └── accounting.json    # 账单数据
│
├── config.py              # 配置文件(常量)
├── file_utils.py          # 文件读写模块
├── logic.py               # 业务逻辑模块(核心算法)
├── ui.py                  # 用户界面模块(输入输出)
└── main.py                # 主程序入口

2.2 模块划分

  1. config.py:定义常量(如文件路径、记账类型)。
  2. file_utils.py:负责数据的加载(Load)和保存(Save)。
  3. logic.py:负责数据的增删改查(CRUD)逻辑。
  4. ui.py:负责展示菜单、接收用户输入。
  5. main.py:负责程序的流程控制(死循环直到用户退出)。

3. 核心代码实现

3.1 数据模型与常量 (config.py)

定义项目中会用到的常量,避免"魔法数字"。

python 复制代码
# config.py
import os

# --- 基础配置 ---
# 获取当前脚本所在目录
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# 数据文件夹路径
DATA_DIR = os.path.join(BASE_DIR, 'data')
# 账单文件路径
ACCOUNT_FILE = os.path.join(DATA_DIR, 'accounting.json')

# --- 记账配置 ---
# 记账类型
TYPES = ['收入', '支出']
# 常用分类
CATEGORIES = {
    '收入': ['工资', '奖金', '理财', '兼职', '其他'],
    '支出': ['餐饮', '交通', '购物', '娱乐', '住房', '通讯', '医疗', '学习', '其他']
}

3.2 文件操作模块 (file_utils.py)

利用 json 模块实现数据的持久化,这里要处理文件不存在的异常。

python 复制代码
# file_utils.py
import json
import os
from config import ACCOUNT_FILE, DATA_DIR

def ensure_data_dir():
    """确保数据目录存在"""
    if not os.path.exists(DATA_DIR):
        os.makedirs(DATA_DIR)

def load_data():
    """
    从文件加载数据
    Returns: 账单列表
    """
    ensure_data_dir()
    # 如果文件不存在,返回空列表
    if not os.path.exists(ACCOUNT_FILE):
        return []
    
    try:
        with open(ACCOUNT_FILE, 'r', encoding='utf-8') as f:
            data = json.load(f)
            # 确保数据是列表格式
            return data if isinstance(data, list) else []
    except (json.JSONDecodeError, Exception) as e:
        print(f"❌ 数据文件读取错误:{e}。将创建新的空账本。")
        return []

def save_data(records):
    """
    保存数据到文件
    Args: records (list): 账单列表
    """
    try:
        with open(ACCOUNT_FILE, 'w', encoding='utf-8') as f:
            json.dump(records, f, ensure_ascii=False, indent=4)
        print("✅ 数据已自动保存!")
    except Exception as e:
        print(f"❌ 数据保存失败:{e}")

3.3 业务逻辑模块 (logic.py)

处理核心数据逻辑,不涉及用户交互。

python 复制代码
# logic.py
import json
from datetime import datetime
from config import TYPES

def add_record(records, type_, amount, category, note="", date_=None):
    """
    添加一条记录
    """
    if date_ is None:
        date_ = datetime.now().strftime("%Y-%m-%d")
    
    # 生成 ID:取最大 ID + 1,如果没有数据则为 1
    new_id = max([r['id'] for r in records] or [0]) + 1
    
    record = {
        "id": new_id,
        "type": type_,
        "amount": amount,
        "category": category,
        "date": date_,
        "note": note
    }
    records.append(record)
    return True

def delete_record(records, record_id):
    """
    删除记录
    """
    initial_len = len(records)
    records[:] = [r for r in records if r['id'] != record_id]
    return len(records) < initial_len

def query_records(records, month=None, category=None):
    """
    查询记录(支持过滤)
    """
    result = records
    
    if month:
        # 格式:2026-04
        result = [r for r in result if r['date'].startswith(month)]
    
    if category:
        result = [r for r in result if r['category'] == category]
    
    return result

def get_statistics(records):
    """
    统计数据
    """
    total_income = sum(r['amount'] for r in records if r['type'] == '收入')
    total_expense = sum(r['amount'] for r in records if r['type'] == '支出')
    balance = total_income - total_expense
    return {
        'total_income': total_income,
        'total_expense': total_expense,
        'balance': balance
    }

3.4 用户交互界面 (ui.py)

负责与用户"对话",这里大量使用 try-except 处理输入错误。

python 复制代码
# ui.py
from logic import get_statistics
from config import TYPES,CATEGORIES
from file_utils import load_data

def show_menu():
    """显示主菜单"""
    print("\n" + "=" * 40)
    print("💰 欢迎使用个人记账管理系统")
    print("=" * 40)
    stats = get_statistics(load_data())  # 这里为了演示,实际应传入内存数据
    print(f"📊 当前余额:{stats['balance']:.2f} (收入:{stats['total_income']:.2f} 支出:{stats['total_expense']:.2f})")
    print("-" * 40)
    print("1. 📝 添加记录")
    print("2. 📋 查看所有记录")
    print("3. 🔍 按条件查询")
    print("4. 🗑️  删除记录")
    print("5. 🚪 退出系统")
    print("-" * 40)


def get_user_choice():
    """获取用户选择"""
    try:
        choice = input("👉 请选择功能 (1-5): ").strip()
        return int(choice)
    except ValueError:
        return -1  # 无效输入


def input_record():
    """交互式输入一条记录"""
    while True:
        print("\n--- 记账类型 ---")
        for i, t in enumerate(TYPES, 1):
            print(f"{i}. {t}")

        try:
            type_idx = int(input("请选择类型 (1-2): ")) - 1
            if type_idx not in [0, 1]:
                raise ValueError
            type_ = TYPES[type_idx]

            amount = float(input(f"请输入{type_}金额: "))
            if amount <= 0:
                raise ValueError("金额必须大于0")

            category = input(f"请输入分类 (如{CATEGORIES[type_][0]}): ").strip()
            note = input("请输入备注 (可选): ").strip()

            return type_, amount, category, note

        except ValueError as e:
            if "could not convert" in str(e):
                print("❌ 错误:请输入有效的数字!")
            else:
                print(f"❌ 输入错误:{e},请重新输入!")

3.5 主程序入口 (main.py)

项目的"大脑",控制程序的流转。

python 复制代码
# main.py
"""
Python 零基础入门系列(终篇):综合实战项目
个人记账管理系统主程序
"""

import os
from file_utils import load_data, save_data
from logic import add_record, delete_record, query_records, get_statistics
from ui import show_menu, get_user_choice, input_record

# --- 全局变量 ---
# 在内存中维护的账单列表
records = []

def main():
    global records
    print("🚀 系统启动中...")
    
    # 1. 加载数据
    records = load_data()
    print(f"💾 已加载 {len(records)} 条历史数据。")
    
    # 2. 主循环
    while True:
        show_menu()
        choice = get_user_choice()
        
        if choice == 1:
            # 添加记录
            print("\n--- 添加新记录 ---")
            data = input_record()
            if data:
                type_, amount, category, note = data
                if add_record(records, type_, amount, category, note):
                    print(f"✅ 成功记录:{type_} {amount}元 ({category})")
        
        elif choice == 2:
            # 查看所有
            print("\n--- 所有账单 ---")
            stats = get_statistics(records)
            for r in records:
                print(f"[{r['date']}] ID:{r['id']} {r['type']} {r['amount']:.2f} ({r['category']}) {r['note']}")
            print(f"--- 总计:收入 {stats['total_income']:.2f} | 支出 {stats['total_expense']:.2f} | 余额 {stats['balance']:.2f} ---")
        
        elif choice == 3:
            # 查询
            month = input("请输入查询月份 (格式 YYYY-MM, 直接回车查询所有): ").strip()
            filtered = query_records(records, month=month if month else None)
            print(f"\n--- 查询结果 ({len(filtered)} 条) ---")
            for r in filtered:
                print(f"{r['date']} {r['type']} {r['amount']:.2f}元 ({r['category']})")
        
        elif choice == 4:
            # 删除
            try:
                rid = int(input("请输入要删除的记录ID: "))
                if delete_record(records, rid):
                    print("✅ 删除成功!")
                else:
                    print("❌ 未找到该ID的记录!")
            except ValueError:
                print("❌ 请输入有效的ID数字!")
        
        elif choice == 5:
            # 退出
            print("👋 感谢使用,正在保存数据...")
            save_data(records)
            print("👋 再见!")
            break
        
        else:
            print("❌ 无效选择,请输入 1-5 之间的数字。")
        
        # 暂停,按回车继续
        input("\n⌨️  按回车键继续...")

if __name__ == "__main__":
    main()

4. 运行与测试

  1. 创建项目文件夹 :新建 Personal_Accounting 文件夹。

  2. 创建文件 :按照上述代码分别创建 config.py, file_utils.py, logic.py, ui.py, main.py

  3. 安装依赖:本项目仅使用标准库,无需额外安装。

  4. 运行

    复制代码
    cd Personal_Accounting
    python main.py
  5. 测试点

    • 启动后是否生成了 data/accounting.json
    • 添加几笔收入和支出,退出后数据是否还在?
    • 输入非数字的金额,程序是否会崩溃?(应该会提示错误并重新输入)

5. 总结与展望

🎉 恭喜你!你已经完成了 Python 零基础入门系列的全部内容!

通过这个《个人记账管理系统》,你不仅复习了:

  • 基础语法:变量、循环、判断。
  • 数据结构:字典、列表、JSON。
  • 函数与模块:代码的封装与复用。
  • 异常处理:让程序更健壮。
  • 文件操作:数据的持久化。

你现在的身份已经从"Python 学习者"转变为"Python 开发者"!

下一步建议

虽然我们完成了基础版,但这个项目还可以继续升级,作为你后续的练手项目:

  1. 美化界面 :使用 PrettyTable 库让表格更美观。
  2. 增加图表 :使用 matplotlib 画出每月支出饼图。
  3. 数据校验:增加日期格式校验。
  4. 数据库:尝试将 JSON 换成 SQLite 数据库存储。
相关推荐
羊小猪~~2 小时前
算法/力扣--栈与队列经典题目
开发语言·c++·后端·考研·算法·leetcode·职场和发展
Noushiki2 小时前
数据一致性保障方案 -java后端
java·开发语言
Evand J2 小时前
【MATLAB例程】基于EKF的分布式卡尔曼滤波,用于多个车辆的集群导航,融合IMU和GNSS、相对测量的UWB数据
开发语言·分布式·matlab
人间打气筒(Ada)2 小时前
go:如何实现接口限流和降级?
开发语言·中间件·go·限流·etcd·配置中心·降级
小陈工2 小时前
Python Web开发入门(二):Flask vs Django,项目结构大比拼
前端·数据库·python·安全·web安全·django·flask
云泽8082 小时前
深入红黑树:SGI-STL 中 map 与 set 的关联容器架构剖析
开发语言·c++·stl底层架构
福楠2 小时前
constexpr 全家桶
c语言·开发语言·c++
REDcker2 小时前
C++ vcpkg:安装、使用、原理与选型
开发语言·c++·windows·操作系统·msvc·vcpkg
晓13132 小时前
React篇——第五章 React Router实战
开发语言·javascript·ecmascript