这一章不学新语法,把前面学的全部串起来,做一个真的能用的程序:记账本。
我会一行行带着你写。写之前先看它长什么样,再拆开讲。
运行起来是这样的(> 后面是你输入的内容):
> 工资
> 8000
> 收入
工资|收入|8000 元
> 房租
> 2000
> 支出
工资|收入|8000 元
房租|支出|2000 元
结余:6000 元
它能:记一笔账(名称、金额、收入还是支出)、看流水、算结余。看着简单,但它会重新用到前面几课学过的大部分核心知识。
记账,核心是一个个「账目」。一个账目有三个信息:
这些信息捆在一起,正好用类进阶学的类来表示。先造「账目」这个零件:
类 账目
初始化(名称, 金额, 种类)
如果 金额 <= 0
抛出 @"金额必须大于 0,{名称}的金额是 {金额}"
函数 描述()
返回 @"{自己.名称}|{自己.种类}|{自己.金额} 元"
逐行看:
类 账目 —— 造一个叫「账目」的模板初始化(名称, 金额, 种类) —— 造账目时要给这三个值,它们自动成为字段(不用写 自己.名称 = 名称)如果 金额 <= 0 里的 抛出 —— 这是异常处理的异常:金额必须正数,否则当场喊停。这条是校验,防止你记一笔「-5 元」的糊涂账描述() —— 方法,把账目变成一行文字,用 @ 格式串(输入输出)验证一下零件能不能用:
下面是接在上面
账目类之后运行的片段;单独复制时要把类定义一起带上。
设 一笔 = 账目("工资", 8000, "收入")
打印(一笔.描述()) # 工资|收入|8000 元
现在要一个东西能装下很多账目。用类进阶讲的「组合」——记账本里放一个列表:
下面只展示新加的
记账本类片段;它会用到上面已经定义的账目类。
类 记账本
初始化()
自己.条目 = []
函数 记一笔(名称, 金额, 种类)
追加(自己.条目, 账目(名称, 金额, 种类))
初始化() 没有参数,但块体不能省——这里要造一个字段 自己.条目 = [](一个空列表)记一笔 方法:造一个新账目,用 追加(列表)塞进列表结余 = 所有收入之和 − 所有支出之和。要「把所有条目过一遍」,是遍历的活。下面只展示要继续放进 类 记账本 的方法片段:
函数 结余()
设 收入 = 0
设 支出 = 0
遍历 自己.条目 每个 条
如果 条.种类 == "收入"
收入 = 收入 + 条.金额
否则
支出 = 支出 + 条.金额
返回 收入 - 支出
遍历 自己.条目 每个 条 —— 把每个账目轮流交给 条条.种类、条.金额 —— 取这个账目对象的字段(. 就像字典的 ["标签"],见字典)如果…否则… —— 做判断的分支收入 = 收入 + 条.金额 —— 累加。也可以写成 收入 += 条.金额(重复做的复合赋值)下面仍是要继续放进 类 记账本 的方法片段:
函数 看流水()
如果 长度(自己.条目) == 0
打印("还没有账目")
返回
遍历 自己.条目 每个 条
打印(条.描述())
长度(...) 是内置函数速查的内置函数。这里先判断「一本空账本」,给个友好提示,而不是啥都不打。
想一眼看出「收入总共多少、支出总共多少」,用字典的字典按种类归拢。下面仍是要继续放进 类 记账本 的方法片段:
函数 按种类汇总()
设 汇总 = {}
遍历 自己.条目 每个 条
如果 有标签(汇总, 条.种类)
汇总[条.种类] += 条.金额
否则
汇总[条.种类] = 条.金额
返回 汇总
逐行拆:
设 汇总 = {} —— 一个空字典,将来是 {"收入": 8500, "支出": 2300} 这种有标签(汇总, 条.种类) —— 这个种类在汇总里已经有过吗?汇总[条.种类] += 条.金额,往已有的数上累加(这是重复做学的「索引复合赋值」)汇总[条.种类] = 条.金额,第一次出现,先建一个这正是字典讲的「分组计数」套路,只是把「计数」换成了「累计金额」。
想只看最近几笔账,用列表的负索引切片。下面仍是要继续放进 类 记账本 的方法片段:
函数 最近几笔(数量)
返回 自己.条目[-数量:]
自己.条目[-数量:] 从倒数第 数量 个取到末尾。比如 [-2:] 就是最后两笔。不用数列表多长,负数自动从尾巴数。
把上面的方法都塞进 类 记账本,加上开头造账本、末尾演示:
类 账目
初始化(名称, 金额, 种类)
如果 金额 <= 0
抛出 @"金额必须大于 0,{名称}的金额是 {金额}"
函数 描述()
返回 @"{自己.名称}|{自己.种类}|{自己.金额} 元"
类 记账本
初始化()
自己.条目 = []
函数 记一笔(名称, 金额, 种类)
追加(自己.条目, 账目(名称, 金额, 种类))
函数 结余()
设 收入 = 0
设 支出 = 0
遍历 自己.条目 每个 条
如果 条.种类 == "收入"
收入 = 收入 + 条.金额
否则
支出 = 支出 + 条.金额
返回 收入 - 支出
函数 看流水()
如果 长度(自己.条目) == 0
打印("还没有账目")
返回
遍历 自己.条目 每个 条
打印(条.描述())
函数 按种类汇总()
设 汇总 = {}
遍历 自己.条目 每个 条
如果 有标签(汇总, 条.种类)
汇总[条.种类] += 条.金额
否则
汇总[条.种类] = 条.金额
返回 汇总
函数 最近几笔(数量)
返回 自己.条目[-数量:]
设 本子 = 记账本()
本子.记一笔("工资", 8000, "收入")
本子.记一笔("房租", 2000, "支出")
本子.记一笔("吃饭", 300, "支出")
本子.记一笔("理财", 500, "收入")
本子.看流水()
打印(@"结余:{本子.结余()} 元")
输出:
工资|收入|8000 元
房租|支出|2000 元
吃饭|支出|300 元
理财|收入|500 元
结余:6200 元
上面的账是写死在代码里的。想让使用者自己输入,用输入输出的 询问 / 询问数值。下面是替换完整程序末尾演示部分的片段,前面的两个类仍要保留:
设 本子 = 记账本()
设 名称 = 询问("名称:")
设 金额 = 询问数值("金额:")
设 种类 = 询问("种类(请输入 收入 或 支出):")
本子.记一笔(名称, 金额, 种类)
本子.看流水()
打印(@"结余:{本子.结余()} 元")
运行时会停下来等你敲字。询问数值 拿回来的是数字,输错还会自动重问;种类请按提示输入 收入 或 支出,这个入门示例暂不增加第三种分类。
如果使用者输入金额 -5,账目 的初始化会 抛出 异常,程序当场崩。用异常处理的 尝试/捕获 兜住。把上一个片段末尾从 本子.记一笔(...) 开始的三行替换成:
尝试
本子.记一笔(名称, 金额, 种类)
本子.看流水()
打印(@"结余:{本子.结余()} 元")
捕获 错
打印("记不了:" + 错)
现在输入 -5,程序不会崩,而是打印:
记不了:金额必须大于 0,白条的金额是 -5
看——你这一章写下的 抛出,在这里被 捕获 接住了。异常处理的两半(抛出 + 捕获)就这么配合起来了。
这个程序用到了前面几乎每一章,写一遍等于复习一遍:
| 用了什么 | 来自哪章 |
|---|---|
类、初始化、自己、方法、字段、组合 |
第五章 |
抛出、尝试/捕获 |
第四章 |
询问、询问数值、@ 格式串 |
第四章 |
遍历 … 每个 … |
第三章 |
字典、有标签、汇总[标签] += 值 |
第三章 |
列表、追加、负索引切片 [-数量:] |
第三章 |
长度 内置函数 |
第四章 |
如果/否则、== 比较 |
第一章 |
累加 收入 = 收入 + 金额 |
第一章 |
学过 Python 的人,看这里——这个记账本,用 Python 的 class 写是这样。零基础可以直接跳过,去下面做「练一练」。
class 账目: # 类 账目
def __init__(self, 名称, 金额, 种类): # 初始化
if 金额 <= 0:
raise ValueError(f"金额必须大于 0,{名称}的金额是 {金额}") # 抛出
self.名称 = 名称 # ← Python 要一行行写,CNplus 自动
self.金额 = 金额
self.种类 = 种类
def 描述(self): # 函数 描述()
return f"{self.名称}|{self.种类}|{self.金额} 元" # @ 格式串 → f-string
对照几个关键点:
| CNplus | Python |
|---|---|
类 账目 |
class 账目 |
初始化(名称, 金额)(参数自动成字段) |
def __init__(self, 名称, 金额) + 手动 self.名称 = 名称 |
自己.名称 |
self.名称 |
@"…{自己.名称}…" |
f"…{self.名称}…" |
抛出 "说明" |
raise ValueError("说明") |
最大的区别还是类进阶说的那三条:初始化参数自动成字段、方法不写 self、没有继承。会用 CNplus 写这个记账本,转 Python 只差「手动写 self.xxx = xxx」这一步。