从零开始学Python(十):实战项目 - 个人记账本

从零开始学Python(十):实战项目 - 个人记账本

本系列教程将带你从零开始学习Python编程,无需任何编程基础。

项目简介

我们将创建一个个人记账本应用程序,帮助你记录日常收入和支出,查看余额,并分析消费情况。

功能需求包括添加交易、查看余额、查看历史、数据分析、数据持久化。技术要点包括使用类和对象进行面向对象编程,使用列表和字典存储交易数据,使用文件操作保存和加载数据,使用异常处理处理用户输入错误,使用控制流程实现菜单循环和条件判断。

完整代码

创建文件 account_book.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# 个人记账本

import json
import os
from datetime import datetime


class Transaction:
"""交易类"""

# 交易类型
TYPE_INCOME = "收入"
TYPE_EXPENSE = "支出"

# 支出类别
EXPENSE_CATEGORIES = ["餐饮", "交通", "购物", "娱乐", "居住", "医疗", "教育", "其他"]

# 收入类别
INCOME_CATEGORIES = ["工资", "奖金", "投资", "兼职", "其他"]

def __init__(self, transaction_type, amount, category, note=""):
"""初始化交易"""
self.date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.type = transaction_type
self.amount = amount
self.category = category
self.note = note

def to_dict(self):
"""转换为字典(用于保存到文件)"""
return {
"date": self.date,
"type": self.type,
"amount": self.amount,
"category": self.category,
"note": self.note
}

@classmethod
def from_dict(cls, data):
"""从字典创建交易对象(用于从文件加载)"""
transaction = cls(
data["type"],
data["amount"],
data["category"],
data.get("note", "")
)
transaction.date = data["date"]
return transaction

def __str__(self):
"""字符串表示"""
sign = "+" if self.type == self.TYPE_INCOME else "-"
return f"{self.date} | {self.type} | {self.category} | {sign}{self.amount:.2f}元 | {self.note}"


class AccountBook:
"""记账本类"""

DATA_FILE = "account_data.json"

def __init__(self):
"""初始化记账本"""
self.transactions = []
self.load_data()

def add_transaction(self):
"""添加交易"""
print("\n" + "=" * 60)
print(" 添加交易")
print("=" * 60)

# 选择交易类型
while True:
print("\n请选择交易类型:")
print("1. 收入")
print("2. 支出")
choice = input("请输入选择(1-2):").strip()

if choice == "1":
trans_type = Transaction.TYPE_INCOME
categories = Transaction.INCOME_CATEGORIES
break
elif choice == "2":
trans_type = Transaction.TYPE_EXPENSE
categories = Transaction.EXPENSE_CATEGORIES
break
else:
print("无效选择,请重试")

# 输入金额
while True:
try:
amount = float(input("\n请输入金额:"))
if amount <= 0:
print("金额必须大于0")
continue
break
except ValueError:
print("请输入有效的数字")

# 选择类别
print(f"\n请选择{trans_type}类别:")
for i, cat in enumerate(categories, 1):
print(f"{i}. {cat}")

while True:
try:
cat_choice = int(input(f"请选择(1-{len(categories)}):"))
if 1 <= cat_choice <= len(categories):
category = categories[cat_choice - 1]
break
else:
print(f"请输入1-{len(categories)}之间的数字")
except ValueError:
print("请输入有效的数字")

# 输入备注(可选)
note = input("\n请输入备注(可选,直接回车跳过):").strip()

# 创建交易并添加
transaction = Transaction(trans_type, amount, category, note)
self.transactions.append(transaction)
print("\n✅ 交易添加成功!")

def view_balance(self):
"""查看余额"""
print("\n" + "=" * 60)
print(" 账户余额")
print("=" * 60)

total_income = 0
total_expense = 0

for trans in self.transactions:
if trans.type == Transaction.TYPE_INCOME:
total_income += trans.amount
else:
total_expense += trans.amount

balance = total_income - total_expense

print(f"\n总收入:{total_income:.2f}元")
print(f"总支出:{total_expense:.2f}元")
print(f"当前余额:{balance:.2f}元")
print("=" * 60)

def view_history(self):
"""查看交易历史"""
print("\n" + "=" * 80)
print(" 交易历史")
print("=" * 80)

if not self.transactions:
print("\n还没有任何交易记录!")
print("=" * 80)
return

# 按时间倒序显示
for i, trans in enumerate(reversed(self.transactions), 1):
print(f"{i}. {trans}")

print("=" * 80)
print(f"共 {len(self.transactions)} 条记录")

def analyze_expenses(self):
"""分析支出"""
print("\n" + "=" * 60)
print(" 支出分析")
print("=" * 60)

# 统计各类别支出
expense_by_category = {}

for trans in self.transactions:
if trans.type == Transaction.TYPE_EXPENSE:
category = trans.category
if category not in expense_by_category:
expense_by_category[category] = 0
expense_by_category[category] += trans.amount

if not expense_by_category:
print("\n还没有支出记录!")
print("=" * 60)
return

# 计算总支出
total_expense = sum(expense_by_category.values())

# 显示统计结果
print(f"\n总支出:{total_expense:.2f}元")
print("\n类别统计:")
print("-" * 60)

# 按金额排序
sorted_categories = sorted(
expense_by_category.items(),
key=lambda x: x[1],
reverse=True
)

for category, amount in sorted_categories:
percentage = (amount / total_expense) * 100
bar_length = int(percentage / 2)
bar = "█" * bar_length
print(f"{category:8s} | {amount:8.2f}元 | {percentage:5.1f}% | {bar}")

print("-" * 60)
print(f"最大支出类别:{sorted_categories[0][0]}")
print("=" * 60)

def save_data(self):
"""保存数据到文件"""
try:
data = [trans.to_dict() for trans in self.transactions]
with open(self.DATA_FILE, "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=2)
print(f"\n✅ 数据已保存到 {self.DATA_FILE}")
except Exception as e:
print(f"\n❌ 保存数据失败:{e}")

def load_data(self):
"""从文件加载数据"""
if not os.path.exists(self.DATA_FILE):
return

try:
with open(self.DATA_FILE, "r", encoding="utf-8") as file:
data = json.load(file)
self.transactions = [Transaction.from_dict(item) for item in data]
print(f"✅ 已加载 {len(self.transactions)} 条交易记录")
except Exception as e:
print(f"❌ 加载数据失败:{e}")
self.transactions = []

def clear_all_data(self):
"""清空所有数据"""
confirm = input("\n⚠️ 确定要清空所有数据吗?此操作不可恢复!(yes/no):")
if confirm.lower() == "yes":
self.transactions = []
self.save_data()
print("✅ 所有数据已清空")
else:
print("取消操作")

def display_menu(self):
"""显示菜单"""
print("\n" + "=" * 60)
print(" 个人记账本")
print("=" * 60)
print("1. 添加交易")
print("2. 查看余额")
print("3. 查看历史")
print("4. 支出分析")
print("5. 保存数据")
print("6. 清空数据")
print("0. 退出")
print("=" * 60)

def run(self):
"""运行主程序"""
print("\n🎉 欢迎使用个人记账本!")

while True:
self.display_menu()
choice = input("\n请选择操作(0-6):").strip()

if choice == "0":
print("\n感谢使用,再见!")
# 退出前自动保存
self.save_data()
break
elif choice == "1":
self.add_transaction()
elif choice == "2":
self.view_balance()
elif choice == "3":
self.view_history()
elif choice == "4":
self.analyze_expenses()
elif choice == "5":
self.save_data()
elif choice == "6":
self.clear_all_data()
else:
print("\n❌ 无效选择,请重试")


def main():
"""主函数"""
app = AccountBook()
app.run()


if __name__ == "__main__":
main()

Transaction类表示单条交易记录,包含日期、类型、金额、类别、备注等属性。to_dict()方法转换为字典便于保存,from_dict()方法从字典创建对象便于加载,str()方法定义字符串表示。

AccountBook类是记账本主程序管理所有交易。add_transaction()添加新交易,view_balance()查看账户余额,view_history()查看交易历史,analyze_expenses()分析支出,save_data()保存到JSON文件,load_data()从JSON文件加载,run()是主循环。

关键技术点包括使用面向对象编程组织代码,使用JSON格式保存和加载数据,处理用户输入错误和文件操作错误,使用列表存储和遍历交易记录,使用字典统计各类别支出,使用字符串格式化美化输出显示。

你可以继续扩展这个项目,添加搜索功能按日期、类别、关键词搜索交易,预算管理设置月度预算超支提醒,使用matplotlib绘制收支图表,支持CSV格式导入导出,支持多个账户如现金银行卡等,自动添加每月固定收支。

数据保存在account_data.json文件中,位于程序同一目录。直接复制JSON文件即可备份数据。如果没有备份数据无法恢复,建议定期备份。

通过这个项目,我们综合运用了Python基础语法、变量与输入、数据类型、控制流程、函数、数据结构、文件操作、异常处理、面向对象等全部知识点。

恭喜你!

你已经完成了《从零开始学Python》全部10章的学习!

你学会了Python基础语法和数据类型,控制流程和函数,数据结构(列表、字典、元组、集合),文件操作和异常处理,面向对象编程,完整项目开发。

接下来你可以继续扩展记账本项目,学习Python进阶主题如装饰器、生成器等,探索Python库如NumPy、Pandas、Django等,参与开源项目。

学习资源包括Python官方文档、Python教程、LeetCode练习编程题、GitHub查看开源项目。记住:编程是一项实践技能,多写代码才能不断进步!

祝你在Python学习之路上一帆风顺!


系列导航