记账本 v1
完整讲解 · 10 段教学·配 4 道练习题·预计 60 分钟
本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。
前言
前面三章,我们学了列表、元组、字典、集合,还学了条件判断和循环。
学到这里,各位童鞋是不是有种感觉:脑子里装了一堆零件,每个零件都认识,可真要拼成一个能用的程序,又不知道从哪儿下手?
其实,这时候缺的往往不是语法,而是拆问题的习惯。
什么叫拆问题呢?就是写程序之前,先用大白话把要做的事一条一条写下来,再看看每一条该用学过的哪个零件。拆清楚了,写代码基本上就是照着翻译。
光说不练可不行。这一章,咱们还是拿实战 1 的记账本来练手。它现在只能记一笔,而且程序一结束,记过的账也就忘了。我们要让它一口气记下一整天的账,最后还能告诉你:钱都花到哪儿去了。
代码会比实战 1 长不少,第一遍写不对很正常。别急着看答案,先对照流程,把程序一步一步走一遍,看看是哪一步出了岔子。自己找出来的问题,比直接看答案记得牢多了。
学这一章之前,需要先学完 实战 1 的记账本 v0,以及第 3~5 章的列表、字典、条件判断和循环。哪里卡住了,就回到对应的章节看一看:第 3 章 、第 4 章 、第 5 章 。
目录
这一章会学到:
- 一、一天要记好几笔,怎么办?——看看记账本 v0 差在哪儿
- 二、先别写代码,把需求拆开——用大白话写出步骤,再画成流程
- 三、一笔账怎么存?——两个列表为什么容易对不上,换成「列表里装字典」
- 四、让程序一直问下去——用
while True加break,输入「完」就结束 - 五、钱都花哪儿了?——用字典按类别汇总
- 六、常见坑
- 七、自己查一次文档——
dict.items()到底返回了什么 - 八、记账本 v1
- 九、小结与回看
学完这一章,你的记账本可以一口气记下一整天的账,列出每一笔明细,再按类别算出各花了多少钱。
一、一天要记好几笔,怎么办?
咱们先回顾一下,实战 1 写出来的记账本 v0 是这样的:
item = input('买了什么:')
cost = float(input('花了多少钱:'))
print(f'记下了:{item},{cost:.2f} 元')奶茶 12.5
输出的结果:
买了什么:奶茶
花了多少钱:12.5
记下了:奶茶,12.50 元问题很明显:它只能记一笔。可一天下来,早饭、地铁、午饭、奶茶……少说也有五六笔。
那么,照着样子多写几遍 input() ,行不行呢?
item1 = input('买了什么:')
cost1 = float(input('花了多少钱:'))
item2 = input('买了什么:')
cost2 = float(input('花了多少钱:'))
item3 = input('买了什么:')
cost3 = float(input('花了多少钱:'))
# ……今天要是记了 8 笔呢?写是能写,可问题马上就来了:
- 每天花几笔是不固定的,今天 3 笔,明天可能 8 笔。代码里到底写几个
input()? - 变量
item1、item2、item3……越写越多,最后算合计时,还得一个一个加起来。
是不是发现,这两个问题,前面几章其实都给过办法?
- 数量不固定、要装很多个东西,用列表。
- 同样的事要做很多遍,用循环。
所以这一章要做的,就是这件事:用列表把账装起来,用循环一笔一笔地问,记完了再汇总。
不过,先别急着写代码。动手之前,还有一件更要紧的事要做,下一节就来说。
二、先别写代码,把需求拆开
上一节说,先别急着写代码。为什么呢?因为很多童鞋一拿到题目,就直接打开编辑器开始敲。敲着敲着就乱了:这个变量放循环里面还是外面?什么时候停下来?合计又在哪儿算?
其实,写代码之前,先用大白话把要做的事写下来,很多问题自己就清楚了。
不信咱们试一试。假设没有电脑,你拿着一个小本子帮朋友记账,会怎么做?
- 准备一个空本子。
- 问朋友:买了什么?
- 朋友说「完了」,就不再问了,直接跳到第 6 步。
- 再问:花了多少钱?
- 把这一笔写到本子上,回到第 2 步接着问。
- 把本子上的每一笔念一遍,再加起来算个总数。
这几步,就是程序要做的事。把它们画成流程,中间那一圈「问了又问」就看得更清楚了:
① 准备一个空账本
② 问:买了什么?
├─ 回答「完」:跳到 ⑤
└─ 其他回答:往下走
③ 问:花了多少钱?
④ 把这一笔记进账本,回到 ②
⑤ 列出每一笔,算出合计画成流程的时候,清单里的第 3 步并进了第 ② 步,变成了它下面的两个分叉,所以流程比清单少了一步。
流程有了,接下来,给每一步找一个学过的零件,也就是看看每一步该用什么来写:
| 要做的事 | 用什么 |
|---|---|
| 准备一个空账本 | 空列表 entries = [] |
| 一遍一遍地问 | 循环 |
| 回答「完」就不问了 | if 判断,再用 break 跳出循环 |
| 记一笔账 | 列表的 append() |
| 算合计 | 再用一次循环,把金额一笔一笔加起来 |
你会发现,表格右边那一栏,全是前面几章学过的东西,没有一样是新的。
不过,还有一个问题表里没回答:「一笔账」本身该怎么存?它有名称、有金额,后面还要加上类别。这几样东西怎么放在一起,就是下一节要解决的问题。
三、一笔账怎么存?
上一节留下了一个问题:一笔账该怎么存?先想想,一笔账至少有两样东西:买了什么、花了多少钱。
最先想到的办法,可能是用两个列表,一个存名称,一个存金额:
items = ['奶茶', '地铁', '午饭']
costs = [12.5, 4, 25]
print(items[0], costs[0])
print(items[2], costs[2])输出的结果:
奶茶 12.5
午饭 25同一个位置上的名称和金额,正好凑成一笔账。看起来没毛病。
可是,要是删掉其中一笔呢?比如地铁那笔记错了,要删掉:
items = ['奶茶', '地铁', '午饭']
costs = [12.5, 4, 25]
del items[1]
print(items[1], costs[1])输出的结果:
午饭 4午饭怎么变成 4 块钱了?
因为只删了 items 里的「地铁」,忘了删 costs 里的 4 ,两个列表一下子就对不上了。而且程序没有报错,又是一个「不报错的 bug」。
说到底,两个列表要时刻保持一致,每次增删都得记着同时改两处,一不留神就会出错。那么,能不能把一笔账的名称和金额绑在一起,放进同一个东西里呢?
能。第 4 章学的字典,干的正是这个活儿:
entry = {'item': '奶茶', 'cost': 12.5}
print(entry['item'])
print(entry['cost'])输出的结果:
奶茶
12.5一个字典就是一笔账,用 'item' 、'cost' 这两个键,就能取出名称和金额。
一笔账是一个字典,那很多笔账呢?就是很多个字典。把它们装进一个列表里,再用 for 一笔一笔地打印出来:
entries = [
{'item': '奶茶', 'cost': 12.5},
{'item': '地铁', 'cost': 4},
{'item': '午饭', 'cost': 25},
]
for entry in entries:
print(f"{entry['item']} {entry['cost']:.2f} 元")输出的结果:
奶茶 12.50 元
地铁 4.00 元
午饭 25.00 元这段代码里,有个小地方要留意一下:f-string 外面的引号,换成了双引号。为什么要换呢?因为花括号里取键用的是单引号,外面要是也用单引号,Python 3.12 之前的版本会直接报语法错误。新版本虽然不报错,可读起来也很容易看混。
再回到删除的问题。这回要删掉地铁那笔,del entries[1] 一下就行。名称和金额是一起删掉的,不可能再对不上了。
算合计也不难,同样是用循环,一笔一笔地加:
entries = [
{'item': '奶茶', 'cost': 12.5},
{'item': '地铁', 'cost': 4},
{'item': '午饭', 'cost': 25},
]
total = 0
for entry in entries:
total = total + entry['cost']
print(f'合计:{total:.2f} 元')输出的结果:
合计:41.50 元total 一开始是 0,循环每转一圈,就把这一笔的金额加上去。是不是和第 5 章算 1 到 100 的和,是一个路子?
账本的样子定下来了。先做一道练习,用 for 循环打印明细、算出合计。下一节,再让程序自己一笔一笔地问。
编辑器里的 entries 存了今天的四笔账,每一笔都是一个字典。请用 for 循环把每一笔打印成一行,金额保留两位小数,同时算出合计,最后打印合计。
运行后的输出应该是:
早饭 8.00 元
地铁 4.00 元
午饭 25.00 元
奶茶 12.50 元
合计:49.50 元💡 提示(3 条)
合计 total = 0 要写在循环前面,只准备一次
循环里用 entry['item'] 和 entry['cost'] 取名称和金额;f-string 外面用双引号,里面取键用单引号
完整代码:
entries = [ {'item': '早饭', 'cost': 8}, {'item': '地铁', 'cost': 4}, {'item': '午饭', 'cost': 25}, {'item': '奶茶', 'cost': 12.5}, ] total = 0 for entry in entries: print(f"{entry['item']} {entry['cost']:.2f} 元") total = total + entry['cost'] print(f'合计:{total:.2f} 元')
四、让程序一直问下去
账本的样子有了,接下来就该让程序一笔一笔地问了。
那要问几次呢?事先并不知道,得等用户说「完」才停。
第 5 章讲 for 循环和 while 循环的区别时说过,while 循环主要用在「满足条件就反复执行」的情况,还顺口提了一句「死循环 + break 退出」。现在用的就是这一招:
entries = []
print('输入「完」结束记账')
while True:
item = input('买了什么:')
if item == '完':
break
cost = float(input('花了多少钱:'))
entries.append({'item': item, 'cost': cost})
print(f'一共记了 {len(entries)} 笔')奶茶 12.5 午饭 25 完
输出的结果:
输入「完」结束记账
买了什么:奶茶
花了多少钱:12.5
买了什么:午饭
花了多少钱:25
买了什么:完
一共记了 2 笔这段代码里,有几个地方值得说一说。
先看 while True 。它的条件永远成立,所以这个循环会一直转下去,一遍一遍地问「买了什么」。
那它什么时候停呢?靠的是循环里的 break 。用户一输入「完」,break 就直接跳出整个循环,去执行循环后面的 print() 。
再看看循环里的顺序:先判断是不是「完」,再问金额。这个顺序可不是随便排的,第六节「常见坑」会讲到,反过来会出什么事。
最后是 append() 。每记一笔,就用它把这笔账的字典加到 entries 的末尾。这样等循环结束的时候,entries 里装的就是今天的全部账了。
在网页里运行时,「程序输入」框里要一行填一个值,顺序是名称、金额、名称、金额……最后一行写「完」。试着多加两笔再运行,看看「一共记了几笔」会不会跟着变。
程序能一直问下去了。先做一道练习,用 while True 把金额一笔一笔地记下来。下一节,再来回答记账本最想回答的问题:钱都花哪儿了?
用 while True 一直问「花了多少钱:」,输入「完」就停下来。最后打印一共记了几笔、合计多少钱,金额保留两位小数。
「程序输入」框里预填了 12.5、4、25 和「完」,运行后完整的输出应该是:
花了多少钱:12.5
花了多少钱:4
花了多少钱:25
花了多少钱:完
一共记了 3 笔,合计 41.50 元检查答案时还会换别的输入,包括一上来就输入「完」的情况,这时应该打印 一共记了 0 笔,合计 0.00 元 。
💡 提示(3 条)
先用 text = input('花了多少钱:') 拿到原样的字符串,判断是不是「完」,确定不是再用 float() 转换
count 和 total 已经在循环前面准备好了,循环里只管往上加:total = total + float(text)、count = count + 1
完整代码:
count = 0 total = 0 while True: text = input('花了多少钱:') if text == '完': break total = total + float(text) count = count + 1 print(f'一共记了 {count} 笔,合计 {total:.2f} 元')
五、钱都花哪儿了?
账是记下来了,可光看一串明细,还是不知道钱都花到哪儿去了。怎么办呢?给每一笔加上类别,比如「餐饮」「交通」,再按类别加起来,就一目了然了。
具体要做两件事。第一件,在每笔账的字典里加一个 'category' 键,记下这笔账的类别。第二件,按类别汇总。汇总要的结果是「餐饮花了多少、交通花了多少」,一个类别对应一个金额,这不就是一个字典吗?所以再准备一个空字典 totals ,用来装汇总的结果:
entries = [
{'item': '奶茶', 'category': '餐饮', 'cost': 12.5},
{'item': '地铁', 'category': '交通', 'cost': 4},
{'item': '午饭', 'category': '餐饮', 'cost': 25},
]
totals = {}
for entry in entries:
category = entry['category']
totals[category] = totals[category] + entry['cost']
print(totals)想法很直接:把这一类原来的钱取出来,加上这一笔,再存回去。第 4 章「同一个科目再学一次,记录怎么更新」就是这么更新的。
可是一运行,报错信息的最后一行是:
KeyError: '餐饮'为什么呢?
第一次遇到「餐饮」的时候,totals 还是空的,里面根本没有 '餐饮' 这个键,totals['餐饮'] 自然取不出来。
那怎么办呢?第 4 章那一节也给过办法:get() 。totals.get(category, 0) 的意思是:有这个类别,就取出它的钱;没有,就当作 0。把出错的那一行改一下试试:
entries = [
{'item': '奶茶', 'category': '餐饮', 'cost': 12.5},
{'item': '地铁', 'category': '交通', 'cost': 4},
{'item': '午饭', 'category': '餐饮', 'cost': 25},
]
totals = {}
for entry in entries:
category = entry['category']
totals[category] = totals.get(category, 0) + entry['cost']
print(totals)输出的结果:
{'餐饮': 37.5, '交通': 4}这回结果对了。这个循环一共转了三圈,咱们一圈一圈地看:
- 第一圈遇到「餐饮」,这时
totals里还没有它,get()给出 0,加上 12.5,存进去。 - 第二圈遇到「交通」,也是第一次见,同样从 0 开始,加上 4。
- 第三圈又遇到「餐饮」,这回取出的是 12.5,再加上 25,就变成了 37.5。
汇总是算对了,可直接打印整个字典,看着不太像账本。最后再用一个 for 循环,把每个类别一行一行地打印出来:
entries = [
{'item': '奶茶', 'category': '餐饮', 'cost': 12.5},
{'item': '地铁', 'category': '交通', 'cost': 4},
{'item': '午饭', 'category': '餐饮', 'cost': 25},
]
totals = {}
for entry in entries:
category = entry['category']
totals[category] = totals.get(category, 0) + entry['cost']
for category in totals:
print(f'{category}:{totals[category]:.2f} 元')输出的结果:
餐饮:37.50 元
交通:4.00 元这里有两点要说明一下。
第一,用 for 遍历字典,拿到的是一个一个的键,所以还要用 totals[category] 把金额取出来。
第二,类别的顺序,和它们第一次出现的顺序一样:先记了餐饮,餐饮就排在前面。
按类别汇总也会了。先做一道练习,自己把汇总写一遍。练完以后,下一节把这一章最容易犯的几个错误,集中看一看。
编辑器里已经写好了记账的循环:先问「类别:」,输入「完」就结束,否则再问「花了多少钱:」。请把每一笔的金额加到字典 totals 对应的类别上,循环结束后,按类别第一次出现的顺序,一行一个类别打印出来,金额保留两位小数。
「程序输入」框里预填了三笔,运行后完整的输出应该是:
类别:餐饮
花了多少钱:12.5
类别:交通
花了多少钱:4
类别:餐饮
花了多少钱:25
类别:完
餐饮:37.50 元
交通:4.00 元检查答案时还会换两组别的输入。
💡 提示(3 条)
直接写 totals[category] + cost 会在第一次遇到某个类别时报 KeyError,用 totals.get(category, 0) 取原来的钱
累加:totals[category] = totals.get(category, 0) + cost;打印时可以用 for category, money in totals.items():
完整代码:
totals = {} while True: category = input('类别:') if category == '完': break cost = float(input('花了多少钱:')) totals[category] = totals.get(category, 0) + cost for category, money in totals.items(): print(f'{category}:{money:.2f} 元')
六、常见坑
记账本要用的零件,前面几节都学过了。可真把它们拼到一起时,有几个错误特别常见,而且有的根本不报错。下面一个一个来看。
1、先转换了,才判断「完」
先看一个顺序上的坑。只记金额的时候,有的童鞋会这样写:
total = 0
while True:
cost = float(input('花了多少钱:'))
if cost == '完':
break
total = total + cost
print(f'合计:{total:.2f} 元')12.5 完
第一笔 12.5 没问题,一输入「完」,报错信息的最后一行是:
ValueError: could not convert string to float: '完'为什么呢?因为 float() 写在了 if 前面。「完」还没来得及判断,就先被拿去转成数字了,当然转不了。
所以顺序要反过来:先拿到原样的字符串,判断是不是「完」。确定不是「完」,再把它转成数字:
total = 0
while True:
text = input('花了多少钱:')
if text == '完':
break
total = total + float(text)
print(f'合计:{total:.2f} 元')12.5 完
输出的结果:
花了多少钱:12.5
花了多少钱:完
合计:12.50 元2、合计放进了循环里
下面这段代码,和上面改好的那段几乎一模一样,只是挪动了一行。先猜一猜:记了三笔以后,合计会是多少?
while True:
total = 0
text = input('花了多少钱:')
if text == '完':
break
total = total + float(text)
print(f'合计:{total:.2f} 元')12.5 4 25 完
输出的结果:
花了多少钱:12.5
花了多少钱:4
花了多少钱:25
花了多少钱:完
合计:0.00 元明明记了三笔,合计怎么是 0?
因为 total = 0 写在了循环里面,每转一圈都被清零一次。最后一圈输入「完」,刚清完零就跳出来了,所以是 0。
记住一句话:只需要做一次的准备工作,放在循环前面。空账本 entries = [] 、合计 total = 0 都是这样。
3、忘了写 break
再看一段只记名称的代码。运行之前,先想一想:它会在哪儿停下来?
entries = []
while True:
item = input('买了什么:')
entries.append(item)
print(entries)奶茶 午饭 完
结果它根本停不下来。程序把三行输入都读完了,接着还在问「买了什么」,最后报错了,报错信息的最后一行是:
EOFError: 程序还想读取输入,但「程序输入」里的内容已经用完了。请在输入框里每行填一个值,再运行一次。为什么会这样?因为循环里没有 break ,while True 就永远停不下来,连「完」都被当成一笔普通的账记了进去。在网页里,是输入用完了,程序没东西可读,才报了错。要是在自己电脑上运行,它会一直问下去,要按 Ctrl + C 才能强行停下来。
所以写 while True 的时候,先想好出口:什么情况下 break ?
4、所有账都变成了同一笔
最后这个坑最隐蔽。下面的代码只在循环前面建了一个字典 entry ,每一圈往里面填名称和金额,再追加到 entries 里:
entries = []
entry = {}
while True:
item = input('买了什么:')
if item == '完':
break
entry['item'] = item
entry['cost'] = float(input('花了多少钱:'))
entries.append(entry)
print(entries)奶茶 12.5 午饭 25 完
输出的结果:
买了什么:奶茶
花了多少钱:12.5
买了什么:午饭
花了多少钱:25
买了什么:完
[{'item': '午饭', 'cost': 25.0}, {'item': '午饭', 'cost': 25.0}]奶茶那一笔怎么没了,变成了两笔午饭?
问题出在 entry = {} 上。它写在循环前面,只执行了一次,所以从头到尾只有这一个字典。每一圈都在改这同一个字典,append() 进去的也一直是它。
第 3 章「名单复制以后,怎么还会一起变」讲过:append() 和赋值都不会复制出新的一份,只是多了一个地方指向同一份数据。所以列表里的两项,其实是同一个字典,显示的自然都是最后一次改成的午饭。
改法是每一圈都新建一个字典,就像第四节那样直接写在 append() 里:
entries.append({'item': item, 'cost': cost})这个坑和第 2 个正好反过来:合计要在循环前面准备,只做一次;而每一笔账的字典,要在循环里面新建,每圈一个。
坑看完了,下一节换一件事:自己查一次文档,把第五节打印类别的那段代码,写得更顺手一点。
七、自己查一次文档
先回头看看,第五节打印每个类别的时候,我们是这么写的:
for category in totals:
print(f'{category}:{totals[category]:.2f} 元')这样写能用,就是有点绕:遍历拿到的只是键,还得再用 totals[category] 把钱取一遍。那么,有没有办法一次就把键和值都拿到呢?
第 4 章字典的函数和方法表里,有一行 dict.items() ,也许用得上。它到底返回的是什么呢?这回咱们自己来查。
先问问 Python 自己:
help(dict.items)说明书的主要内容是这样的(不同版本的 Python,显示得略有差别):
items(...) unbound builtins.dict method
D.items() -> a set-like object providing a view on D's items说明书就一句,还挺抽象:返回一个「像集合一样的对象」,提供字典里各项的「视图」。
看不太懂?没关系,再去官方文档查一查中文版:dict.items() 。文档里说,它返回由字典项,也就是「(键,值) 对」组成的一个新视图。
还是不够直观?那就用最笨、也最可靠的办法:打印出来看看。
totals = {'餐饮': 37.5, '交通': 4}
print(totals.items())输出的结果:
dict_items([('餐饮', 37.5), ('交通', 4)])这下清楚了:里面是一个一个的元组,每个元组都是 (键, 值) 。外面包着的 dict_items(...) ,就是文档说的「视图」。它不是真正的列表,但可以像列表一样,用 for 一个一个地取出来。
每次取出来的都是一个元组。那怎么把元组里的键和值分开呢?其实,元组可以一次拆给两个变量:
category, money = ('餐饮', 37.5)
print(category)
print(money)输出的结果:
餐饮
37.5第 5 章讲 match / case 时见过的「解包」,就是这个意思。
items() 能一对一对地取,元组又能一次拆开。把这两样合起来,遍历的时候就能同时拿到类别和金额:
totals = {'餐饮': 37.5, '交通': 4}
for category, money in totals.items():
print(f'{category}:{money:.2f} 元')输出的结果:
餐饮:37.50 元
交通:4.00 元和原来的写法比一比,少取了一次 totals[category] ,读起来也更直白。
回头看看这次查文档,用的还是这三步:
- 先
help()一下。 - 看不懂,就去查官方的中文文档。
- 还拿不准,就打印出来看看。
这个更顺手的写法,下一节的记账本 v1 马上就会用上。
八、记账本 v1
零件都备齐了,现在把它们拼起来。和前面几节比,这回每一笔多问了一项「类别」。记账本 v1 是这样的:
entries = []
print('输入「完」结束记账')
while True:
item = input('买了什么:')
if item == '完':
break
category = input('类别:')
cost = float(input('花了多少钱:'))
entries.append({'item': item, 'category': category, 'cost': cost})
print('----- 明细 -----')
number = 0
total = 0
totals = {}
for entry in entries:
number = number + 1
print(f"{number}. {entry['item']}({entry['category']}){entry['cost']:.2f} 元")
total = total + entry['cost']
totals[entry['category']] = totals.get(entry['category'], 0) + entry['cost']
print('----- 汇总 -----')
for category, money in totals.items():
print(f'{category}:{money:.2f} 元')
print(f'一共 {total:.2f} 元')奶茶 餐饮 12.5 地铁 交通 4 午饭 餐饮 25 完
输出的结果:
输入「完」结束记账
买了什么:奶茶
类别:餐饮
花了多少钱:12.5
买了什么:地铁
类别:交通
花了多少钱:4
买了什么:午饭
类别:餐饮
花了多少钱:25
买了什么:完
----- 明细 -----
1. 奶茶(餐饮)12.50 元
2. 地铁(交通)4.00 元
3. 午饭(餐饮)25.00 元
----- 汇总 -----
餐饮:37.50 元
交通:4.00 元
一共 41.50 元代码看着长,可别被吓到。它其实就两大块,和第二节画的流程一一对得上:
- 上半部分负责「记」:
while True一直问,输入「完」就break,每一笔都存成字典,追加到entries里。 - 下半部分负责「算」:一个
for循环同时干三件事。它给每一笔编号并打印出来,把金额累加到合计里,再按类别累加。循环结束以后,用上一节查到的items()打印每个类别,最后打印合计。
这里面,编号用的 number 是个新面孔。不过它和合计 total 是一个路子:都在循环前面从 0 开始,每转一圈加一次。
记账本 v1 已经能用了,不过还有不少毛病。比如金额要是输成了「十二块」,程序马上就崩了。再比如,程序一关,记的账也全没了。这些问题,后面的实战章会一个一个解决。
先来做本章最后一道题:给记账本加一个预算提醒。
今天的预算是 50 元。编辑器里已经写好了记账的部分,请接着写汇总:
- 按类别打印每一类花了多少钱,一行一类。
- 最后一行打印合计,再看有没有超出预算:没超出(包括刚好花完)打印「还剩 xx.xx 元」,超出了打印「超支 xx.xx 元」。
金额都保留两位小数。「程序输入」框里预填了三笔,运行后完整的输出应该是:
买了什么:奶茶
类别:餐饮
花了多少钱:12.5
买了什么:地铁
类别:交通
花了多少钱:4
买了什么:午饭
类别:餐饮
花了多少钱:25
买了什么:完
餐饮:37.50 元
交通:4.00 元
一共 41.50 元,还剩 8.50 元检查答案时还会用一组超支的输入,以及一上来就输入「完」的情况。
💡 提示(3 条)
先用一个 for 循环遍历 entries,同时累加合计 total 和按类别的 totals(用 totals.get(类别, 0))
汇总打印完以后,用 if total > budget: 判断:超出就算 total - budget,否则算 budget - total
完整代码:
budget = 50 entries = [] while True: item = input('买了什么:') if item == '完': break category = input('类别:') cost = float(input('花了多少钱:')) entries.append({'item': item, 'category': category, 'cost': cost}) total = 0 totals = {} for entry in entries: total = total + entry['cost'] totals[entry['category']] = totals.get(entry['category'], 0) + entry['cost'] for category, money in totals.items(): print(f'{category}:{money:.2f} 元') if total > budget: print(f'一共 {total:.2f} 元,超支 {total - budget:.2f} 元') else: print(f'一共 {total:.2f} 元,还剩 {budget - total:.2f} 元')
九、小结与回看
这一章,记账本从只能记一笔,变成了能记下一整天的账。回头梳理一下,学到了这几件事:
- 写代码之前,先用大白话把步骤写下来,再画成流程。每一步对上一个学过的零件,写代码就是照着翻译。
- 一笔账有好几样东西,就用字典把它们绑在一起,很多笔账再用列表装起来。要是拆成两个列表分开存,增删的时候很容易对不上。
- 不知道要问几次,就用
while True一直问,满足条件时用break跳出。读进来的输入,要先判断,再转换。 - 只做一次的准备工作,比如
entries = []、total = 0,放在循环前面。而每一笔账的字典,要在循环里面新建。 - 按类别汇总用字典,
totals.get(category, 0)解决第一次遇到某个类别时还没有这个键的问题。 - 遍历字典时想同时拿到键和值,用
items()。
哪里卡住了,就回到对应的章节看一看:
- 列表的增删和
append():第 3 章「一、List(列表)」 - 为什么改一处、另一处跟着变:第 3 章「名单复制以后,怎么还会一起变」
- 字典的取值、更新和
get():第 4 章「一、字典(Dictionary)」 while循环和break:第 5 章「二、循环语句」
回头看看,记账本 v1 已经有二十多行了,比实战 1 长了好几倍。可你会发现,里面没有一行是新语法,全是前面几章学过的东西。这就是拆问题的好处:大问题拆小了,每一小块都是你会的。
当然,它还有毛病。金额要是输成了「十二块」,程序直接就崩了,屏幕上冒出一大段报错。接下来的第 6 章讲函数。学完之后,在实战 3 里,咱们专门来对付报错:看看那一大段报错到底在说什么,又该怎么让程序别那么容易崩。
读完了?动手练一遍才算真会。
去闯关模式练习 →