系列文章
Python 从入门到精通
第 12 / 12 篇
从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。
-
01
Python 怎么装、怎么用 -
02
变量、数字和字符串 -
03
列表:把一组数据放在一起 -
04
元组、集合和字典 -
05
条件判断:让程序做选择 -
06
循环:重复的事交给程序 -
07
函数:把代码整理成可复用的块 -
08
模块与包:拆分你的程序 -
09
输入、输出与字符串格式化 -
10
文件读写:保存程序的数据 -
11
异常处理:程序出错时怎么办 -
12
基础阶段练习:命令行记账本正在阅读
基础阶段的知识点学得差不多了:变量、四种容器、条件、循环、函数、模块、文件、异常。学完不用一次,等于白学。这一篇来一次综合实战:命令行记账本——把前面所有知识串起来,做一个真正能用的程序。
需求分析
先想清楚要做什么(写程序的第一步永远是需求):
- 记一笔账:输入金额、类别(吃饭/交通/购物…)、备注
- 查看所有账单
- 算总支出、按类别统计
- 数据要存到 CSV 文件里,退出程序不丢失
功能菜单长这样:
===== 南山记账本 =====
1. 记一笔
2. 查看账单
3. 分类统计
4. 退出
请选择:
第一步:搭架子
先把菜单循环搭起来——用 while 循环 + 字典存功能,每个选项调一个函数(函数先写占位):
import csv
from pathlib import Path
FILE = Path("ledger.csv")
CATEGORIES = ["吃饭", "交通", "购物", "娱乐", "其他"]
def add_record():
pass # 稍后实现
def show_records():
pass # 稍后实现
def show_stats():
pass # 稍后实现
def main():
while True:
print("n===== 南山记账本 =====")
print("1. 记一笔")
print("2. 查看账单")
print("3. 分类统计")
print("4. 退出")
choice = input("请选择:")
if choice == "1":
add_record()
elif choice == "2":
show_records()
elif choice == "3":
show_stats()
elif choice == "4":
print("再见!")
break
else:
print("无效选择,请输入 1-4")
if __name__ == "__main__":
main()
注意:while True + break 是菜单程序的经典结构。先把骨架跑起来,再一个个填函数——先跑通,再完善。
第二步:记一笔(input + 列表 + 异常)
def add_record():
# 输入金额,处理非法输入
while True:
try:
amount = float(input("金额:"))
if amount <= 0:
print("金额要大于 0")
continue
break
except ValueError:
print("请输入数字")
# 选类别
print("类别:" + " ".join(f"{i}.{c}" for i, c in enumerate(CATEGORIES, 1)))
while True:
try:
idx = int(input("选类别编号:"))
if idx < 1 or idx > len(CATEGORIES):
print("请输入正确的编号")
continue
category = CATEGORIES[idx - 1]
break
except ValueError:
print("请输入数字")
note = input("备注(可留空):")
# 写入 CSV(追加模式)
with open(FILE, "a", newline="", encoding="utf-8") as f:
csv.writer(f).writerow([category, amount, note])
print("已记录 ✓")
这里用到了:try/except 处理输入、enumerate 生成编号、join 拼菜单、with + csv 写文件。全是前面学过的。
第三步:查看账单(读文件 + 格式化输出)
def load_records():
"""读 CSV,返回记录列表;文件不存在返回空列表"""
if not FILE.exists():
return []
with open(FILE, newline="", encoding="utf-8") as f:
return list(csv.reader(f))
def show_records():
records = load_records()
if not records:
print("还没有账单,先记一笔吧")
return
print(f"{'类别':<6}{'金额':>8} 备注")
print("-" * 30)
total = 0
for category, amount, note in records:
print(f"{category:<6}{float(amount):>8.2f} {note}")
total += float(amount)
print("-" * 30)
print(f"合计:{total:.2f} 元")
这里把”读文件”抽成了独立函数 load_records(),后面统计也要用——重复的代码抽成函数,这就是模块化的意义。
第四步:分类统计(字典 + 循环)
def show_stats():
records = load_records()
if not records:
print("还没有数据")
return
stats = {} # 字典:类别 → 金额累计
for category, amount, note in records:
stats[category] = stats.get(category, 0) + float(amount)
print("各分类支出:")
for category, total in stats.items():
print(f" {category}: {total:.2f} 元")
# 找出花最多的类别
top = max(stats, key=stats.get)
print(f"花费最多:{top}({stats[top]:.2f} 元)")
stats.get(category, 0) 是字典计数的经典写法:有就加,没有就初始化为 0。 max(stats, key=stats.get) 找出值最大的键。
完整运行效果
===== 南山记账本 =====
1. 记一笔
2. 查看账单
3. 分类统计
4. 退出
请选择:1
金额:12.5
类别:1.吃饭 2.交通 3.购物 4.娱乐 5.其他
选类别编号:1
备注(可留空):午饭
已记录 ✓
请选择:2
类别 金额 备注
------------------------------
吃饭 12.50 午饭
合计:12.50 元
还能怎么改进
这个版本能用,但还可以加:
- 删账单:按行号删除(用到 enumerate 和列表操作)
- 按日期记录:自动加上 datetime 今天日期(下下篇讲)
- 月度统计:只统计本月的
- 数据校验:金额超过 10000 提醒确认
建议自己动手加一两个功能——改别人的需求,是最快的进步方式。
小结(基础阶段收官)
这一篇我们实际走了一遍完整流程:需求分析 → 骨架先行 → 分函数实现 → 测试运行。回头看,用到的基础知识:
- 变量与类型转换(金额 float)
- 列表、字典(存储、统计)
- 条件与循环(菜单、输入校验)
- 函数(模块化拆分)
- 文件与 CSV(持久化)
- 异常处理(输入容错)
恭喜,基础阶段通关。接下来进入进阶阶段:面向对象、迭代器、装饰器……继续。
练习
- 给记账本加一个”删除账单”功能:查看时显示行号,输入行号删除对应记录(提示:重写整个 CSV 文件)
- 给记账本加一个”收入”类别,统计时分别算收入和支出
- 思考:为什么记账本要把”读文件”抽成
load_records()函数而不是在两个功能里各写一遍?如果需求改成”金额单位从元变成角”,哪个写法改起来省事?