读懂报错与调试
完整讲解 · 10 段教学·配 4 道练习题·预计 60 分钟
本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。
前言
实战 2 写出了记账本 v1,前面又学了函数,咱们写的代码是越来越长了。
代码一长,碰到报错的机会就多了。不知道各位童鞋是不是这样:程序一报错,屏幕上冒出一大段英文,心里先慌了一半。然后要么把整段复制去搜,要么干脆从头再敲一遍,祈祷这次别出错。
其实,报错信息一点都不可怕。它就像医生开的诊断书,出了什么毛病、毛病在哪一行,都写得清清楚楚,就差直接告诉你怎么改了。可惜很多人从来没有认真读过它。
这一章,咱们不学新语法,专门练一件事:读懂报错,自己把 bug 找出来。
这件事没有捷径,只能多看、多练。一开始读得慢没关系,读多了你会发现,常见的报错翻来覆去就那么几种,看一眼最后一行,心里就有数了。
学这一章之前,需要先学完 实战 2 的记账本 v1,以及 第 6 章 的函数:怎么定义函数、怎么传参数、怎么用 return 返回结果。
目录
这一章会学到:
- 一、记账本崩了——输错一个金额,程序就停了
- 二、报错信息从下往上读——错在哪一行,又是怎么走到这一行的
- 三、最常见的几种报错——看最后一行,就知道是什么毛病
- 四、输错了,别崩——先检查,再转换
- 五、不报错的 bug,怎么找?——把变量打印出来看
- 六、常见坑
- 七、自己查一次文档——遇到没见过的报错怎么办
- 八、记账本 v1.1
- 九、小结与回看
学完这一章,你能读懂报错信息,自己找到 bug 在哪儿。记账本也会升级到 v1.1,金额输错了不会再崩,而是客客气气地请你重新输入。
一、记账本崩了
实战 2 的结尾说过,记账本有个大毛病:金额一输错,程序就崩。先来看看,它崩的时候是什么样子:
item = input('买了什么:')
cost = float(input('花了多少钱:'))
print(f'记下了:{item},{cost:.2f} 元')奶茶 十二块
金额那里,输入的是「十二块」。在网页里运行的话,程序不会打印「记下了」,而是给出这样一段:
买了什么:奶茶
花了多少钱:十二块
Traceback (most recent call last):
File "main.py", line 2, in <module>
cost = float(input('花了多少钱:'))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: could not convert string to float: '十二块'是不是有点头大?一堆英文,又是 File 又是 line 的。
先别慌。这一段其实只说了三件事:
- 出了什么错:看最后一行。
ValueError,没法把字符串'十二块'转成浮点数。 - 错在哪儿:看
File "main.py", line 2这一行和它下面贴出来的代码。main.py是网页给你的代码起的文件名,出错的是第 2 行,也就是cost = float(input('花了多少钱:'))。下面那一串^,指着出错的那一段。 - 是怎么走到这儿的:
Traceback下面那几行,记录了程序是一层一层怎么调用过来的。这个例子很简单,只有一层。
剩下的,都是细节。不过,要是程序里用了函数,报错信息还会再长一截。下一节,咱们就换一个带函数的例子,一行一行地把它拆开。
二、报错信息从下往上读
动手拆之前,先认识一下它的名字。报错信息有个正式的名字,叫 traceback,直译过来是「回溯」,意思是把程序出错之前走过的路,一步一步倒着列出来。
下面就拿一个带函数的例子来看。把记一笔账的步骤包成函数 add_entry() ,第二次调用的时候,金额写成了中文的「二十五」:
def add_entry(entries, item, text):
cost = float(text)
entries.append({'item': item, 'cost': cost})
entries = []
add_entry(entries, '奶茶', '12.5')
add_entry(entries, '午饭', '二十五')
print(entries)把它存成 ledger.py ,在自己电脑上运行,看到的是这样的(文件路径每台电脑都不一样,这里用 ... 代替):
Traceback (most recent call last):
File ".../ledger.py", line 8, in <module>
add_entry(entries, '午饭', '二十五')
File ".../ledger.py", line 2, in add_entry
cost = float(text)
^^^^^^^^^^^
ValueError: could not convert string to float: '二十五'第一行 Traceback (most recent call last) 的意思是「最近的一次调用在最后」。所以读的时候,要从下往上读:
- 最后一行:错误类型和说明。
ValueError是「值不对」,后面说没法把'二十五'转成浮点数。 - 往上一层:
line 2, in add_entry,错误发生在add_entry函数里,第 2 行。下面还贴出了这一行代码cost = float(text)。Python 3.11 及以上的版本,还会在出错的地方下面画一串^。 - 再往上一层:
line 8, in <module>,是第 8 行调用了add_entry(),才走到那里的。<module>指的是最外层的代码,不在任何函数里面。
连起来读就是:第 8 行调用 add_entry() → 进到函数里的第 2 行 → float('二十五') 出错了。
这么一读,问题也就清楚了:函数本身没毛病,是第 8 行传进去的金额不对。
那在网页里运行同一段代码,看到的又是什么样子呢?
Traceback (most recent call last):
File "main.py", line 8, in <module>
add_entry(entries, '午饭', '二十五')
File "main.py", line 2, in add_entry
cost = float(text)
^^^^^^^^^^^
ValueError: could not convert string to float: '二十五'对比一下就会发现,它和自己电脑上的几乎一模一样,只有文件名不同。因为网页里的代码不是存在你电脑上的文件,所以统一叫 main.py ,前面也没有文件夹路径。看到 line 8 ,就去编辑器里数到第 8 行。
不管在哪儿运行,读法都一样:先看最后一行是什么错,再往上找你自己代码里的行号。
这一章里会报错的示例代码,页面上没有放运行按钮。想亲眼看看报错长什么样,可以把代码复制到练习题的编辑器里运行,也可以存成文件,在自己电脑上运行。下面的第一道题,就是一段一运行就报错的代码。
先去做这道题,照着报错把 bug 修好。做完了,下一节再来认一认初学时最常碰到的几种报错。
编辑器里的代码有两个 bug,一运行就报错。
先运行,读报错信息:最后一行是什么错?往上找,是你代码里的第几行、在哪个函数里?一次只改一处,改完马上再运行。
两处都改好后,运行的输出应该是:
一共 2 笔,合计 37.50 元💡 提示(3 条)
第一次运行,最后一行是 KeyError: 'cots',往上一层是 summarize 函数里的第 4 行。字典里存金额的键叫什么?
改好第一处再运行,会换成一个新的报错:NameError,后面的 Did you mean 已经告诉你该叫什么了
完整代码:
def summarize(entries): total = 0 for entry in entries: total = total + entry['cost'] print(f'一共 {len(entries)} 笔,合计 {total:.2f} 元') entries = [ {'item': '奶茶', 'cost': 12.5}, {'item': '午饭', 'cost': 25}, ] summarize(entries)
三、最常见的几种报错
上一节说,读报错要先看最后一行。那最后一行会出现哪些错误类型呢?种类看着很多,可初学阶段常碰到的,翻来覆去就这么几种。认得了它们,看一眼最后一行的错误类型,心里就有个大概了:
| 错误类型 | 大概意思 | 记账本里的例子 |
|---|---|---|
SyntaxError | 语法错误,代码写得不合规矩 | 漏了冒号、括号没配对、用了中文标点 |
IndentationError | 缩进不对 | for 下面那一行忘了缩进 |
NameError | 名字没定义 | 变量名打错了,total 写成了 totle |
TypeError | 类型不对 | 数字和字符串相加 |
ValueError | 类型对了,值不对 | float('十二块') |
KeyError | 字典里没有这个键 | 第一次遇到某个类别,就去取它的钱 |
IndexError | 列表的位置超出了范围 | 只有 2 笔账,却去取 entries[2] |
ZeroDivisionError | 除数是 0 | 一笔都没记,就去算平均每笔多少钱 |
后面几种,前面几章或多或少都碰到过。这里重点说说最特别的一种:SyntaxError 。它特别在哪儿呢?看个例子就知道了。
1、语法错误:一行都不会运行
先猜一猜:下面这段代码运行以后,第一行的 print('开始记账') 能不能打印出来?
print('开始记账')
total = 0
for cost in [12.5, 4]
total = total + cost
print(total)报错信息是这样的:
File "main.py", line 3
for cost in [12.5, 4]
^
SyntaxError: expected ':'答案是不能。注意看,第一行明明是 print('开始记账') ,却什么都没打印出来。而且连 Traceback 那一行也没有,因为程序还没开始调用任何东西。
为什么呢?因为 Python 运行代码之前,会先把整段代码从头到尾检查一遍语法。只要有一处不合规矩,就一行都不运行,直接报 SyntaxError 。而 NameError 、ValueError 这些,都是运行到那一行才发现的,所以出错之前的代码照样会执行。
好在语法错误的说明一般都很直白。expected ':' 就是「这里应该有个冒号」,下面的 ^ 指着它觉得该加冒号的位置,也就是第 3 行 for 那一行的末尾。
2、中文标点:看着一样,其实不一样
漏了冒号,还算好发现。还有一种语法错误,代码乍一看一点毛病都没有:
print('你好')报错信息是这样的:
File "main.py", line 1
print('你好')
^
SyntaxError: invalid character '(' (U+FF08)invalid character 是「无效的字符」。原来,括号用成了中文的全角括号 ( ,Python 不认识它。
用中文输入法敲代码,括号、引号、冒号、逗号都很容易打成全角的。看着差不多,Python 可分得清清楚楚。所以敲代码的时候,记得先把输入法切到英文。
3、报错的行号,有时要往上看
再看一个和括号有关的例子:
cost = float(input('花了多少钱:')
print(cost)报错信息的最后一行是:
SyntaxError: '(' was never closed意思是「这个左括号一直没关上」。数一数就知道了:float( 和 input( 两个左括号,只配了一个右括号。
这回 Python 指得挺准。不过,这种「少了东西」的错误,有时要读到后面才能发现,报出来的行号会比真正出错的地方靠后。所以,如果报错的那一行怎么看都没毛病,就往上看一两行。
到这里,报错能读懂了,常见的几种也认得了。下一节,咱们回到第一节那个崩掉的记账本,想办法让它不再崩。
四、输错了,别崩
报错会读了,接下来就回到第一节,解决记账本最常见的那个崩溃:金额输错了。
思路很简单:先检查,再转换。确定输入的是数字,才交给 float() 。
那怎么检查一个字符串是不是全是数字呢?字符串有个方法,叫 isdigit() 。拿几个字符串试一试:
print('12'.isdigit())
print('十二'.isdigit())
print(''.isdigit())
print('12.5'.isdigit())输出的结果:
True
False
False
False前三个都符合预期,连空字符串都不算数字。可 '12.5' 怎么也是 False ?
遇到拿不准的地方,就用实战 1 学过的 help() ,看看它的说明书怎么说:
help(str.isdigit)说明书的主要内容是这样的:
isdigit(self, /) unbound builtins.str method
Return True if the string is a digit string, False otherwise.
A string is a digit string if all characters in the string are digits and there
is at least one character in the string.关键在第二句:字符串里的所有字符都是数字,而且至少有一个字符,才返回 True 。小数点 . 不是数字,所以 '12.5' 不合格。
可记账总会碰上带小数的金额,怎么办呢?
有个小技巧:先去掉一个小数点,再检查剩下的是不是全是数字。
那怎么去掉一个字符呢?用字符串的 replace() 方法。replace('.', '', 1) 的意思是:把 . 换成空字符串,最多换 1 个。试一试:
print('12.5'.replace('.', '', 1))
print('12.5'.replace('.', '', 1).isdigit())
print('1.2.3'.replace('.', '', 1).isdigit())输出的结果:
125
True
False三行输出,咱们一行一行地看:
- 第一行:
'12.5'去掉小数点,变成了125。 - 第二行:
125全是数字,所以检查的结果是True。 - 第三行:
'1.2.3'去掉一个小数点以后,还剩一个,照样不合格,这正是我们想要的。
另外,负数前面的 - 也不是数字,所以负数也会被挡在外面。记花销正好用不上负数,这样反倒合适。
检查的办法有了,再把检查和转换包成一个函数 read_cost() 。它要做的事是:输入不对,就提醒一句,再问一遍,直到输对为止:
def read_cost():
while True:
text = input('花了多少钱:').strip()
if text.replace('.', '', 1).isdigit():
return float(text)
print('请输入数字,比如 12 或 12.5')
cost = read_cost()
print(f'记下了 {cost:.2f} 元')十二块 12.5
输出的结果:
花了多少钱:十二块
请输入数字,比如 12 或 12.5
花了多少钱:12.5
记下了 12.50 元这个函数里,有两个地方要说一下。
一个是 return ,它是第 6 章学的。return 一执行,函数就结束了,while True 也就跟着停了。所以这个循环的出口,就是输对数字的那一刻。
另一个是 .strip() ,它是实战 1 讲过的。先把前后的空格去掉,免得 ' 12' 这样的输入被误判。
其实,Python 还有一种更通用的办法:先大胆地转换,出错了再把错误接住。那要用到 try / except ,第 19 章会专门讲。现在用 if 先检查,对记账本来说已经够用了。
先做一道练习,自己把 read_cost() 写一遍。下一节,咱们再回过头来,对付一种更难缠的 bug:不报错的那种。
补全函数 read_cost() :一直问「花了多少钱:」,先用 .strip() 去掉前后的空格;如果是数字(可以带一个小数点),就返回转换好的浮点数;如果不是,打印 请输入数字,比如 12 或 12.5 ,再问一遍。
「程序输入」框里预填了 12、十二块、12.5,运行后完整的输出应该是:
花了多少钱:12
花了多少钱:十二块
请输入数字,比如 12 或 12.5
花了多少钱:12.5
合计:24.50 元检查答案时还会用空行、1.2.3 、前后带空格的数字这些输入。
💡 提示(3 条)
函数里用 while True 一直问;输对了就 return,return 会让函数直接结束,循环也就停了
检查是不是数字:text.replace('.', '', 1).isdigit(),先去掉一个小数点再检查
完整代码:
def read_cost(): while True: text = input('花了多少钱:').strip() if text.replace('.', '', 1).isdigit(): return float(text) print('请输入数字,比如 12 或 12.5') a = read_cost() b = read_cost() print(f'合计:{a + b:.2f} 元')
五、不报错的 bug,怎么找?
报错的 bug 其实好找,因为 Python 会告诉你错在哪儿。最麻烦的,是不报错、结果却不对的 bug。实战 1 就说过,这种 bug 可能很久都没人发现。
那这种 bug 该怎么找呢?来看一个例子。记了三笔账,想算算餐饮一共花了多少:
entries = [
{'item': '奶茶', 'category': '餐饮', 'cost': 12.5},
{'item': '地铁', 'category': '交通', 'cost': 4},
{'item': '午饭', 'category': '餐饮 ', 'cost': 25},
]
food = 0
for entry in entries:
if entry['category'] == '餐饮':
food = food + entry['cost']
print(f'餐饮一共 {food:.2f} 元')输出的结果:
餐饮一共 12.50 元奶茶 12.5 加午饭 25,应该是 37.50 才对。午饭那笔去哪儿了?
代码看了好几遍,好像都没毛病。这时候,别光盯着代码看了,把变量打印出来。
找这种 bug,可以分四步走:
- 说出你期望的结果:餐饮的那两笔,都应该加进
food。 - 在关键的地方打印:在
if前面,把每一圈的类别打印出来。 - 找到第一个和期望不一样的地方。
- 改掉它,再运行一次。
下面就照着这四步来。期望的结果,上面已经说清楚了。接下来在循环里加一行,用实战 1 用过的 f"{x=}" ,把每一圈的类别打印出来:
entries = [
{'item': '奶茶', 'category': '餐饮', 'cost': 12.5},
{'item': '地铁', 'category': '交通', 'cost': 4},
{'item': '午饭', 'category': '餐饮 ', 'cost': 25},
]
food = 0
for entry in entries:
print(f"{entry['category']=}")
if entry['category'] == '餐饮':
food = food + entry['cost']
print(f'餐饮一共 {food:.2f} 元')输出的结果:
entry['category']='餐饮'
entry['category']='交通'
entry['category']='餐饮 '
餐饮一共 12.50 元然后,找第一个和期望不一样的地方。看输出的第三行:'餐饮 ' 的引号里,「餐饮」后面多了一个空格。'餐饮 ' 和 '餐饮' 是两个不同的字符串,if 判断不成立,午饭那笔就被漏掉了。
从这里也能看出 f"{x=}" 好用在哪儿。如果只是 print(entry['category']) ,屏幕上显示的就是「餐饮」,后面的空格根本看不出来。打印成 x='...' 的样子,引号就把空格露出来了。
最后一步,改掉它。这种多出来的空格,多半是输入的时候不小心敲进去的。所以最好的改法,是在读输入的时候就把它去掉:
category = input('类别:').strip()找到 bug、改好以后,记得把调试用的那行 print() 删掉。
先做一道练习,亲手把这个看不见的空格找出来。下一节,再把读报错、找 bug 时最容易犯的几个错误,集中说一说。
六、常见坑
读报错、找 bug 的办法都学了。可真用起来,下面这几个坑还是很容易踩。
1、只看第一行,不看最后一行
traceback 是从上往下打印的,很多童鞋习惯从第一行开始读。可调用的层数一多,前面全是 File 、line ,读着读着就晕了,最要紧的那一行反倒没看见。
记住顺序:先看最后一行,知道是什么错;再往上找你自己代码里的行号。
2、方法忘了加括号
下面这段代码想判断输入的是不是数字。运行一下,看看输入「十二块」会怎么样:
text = input('花了多少钱:')
if text.isdigit:
print('是数字')
else:
print('不是数字')十二块
输出的结果:
花了多少钱:十二块
是数字「十二块」怎么成了数字?
因为 text.isdigit 后面少了一对括号。没有括号,就不是在调用这个方法,拿到的只是方法本身。if 遇到它,一律当成成立,所以不管输入什么,都打印「是数字」。
而且这个坑不报错,又是一个安安静静的 bug。改成 text.isdigit() 就好了。
3、一次改好几处
一看报错,觉得这里也不对、那里也可疑,一口气改了五六处。结果报错是变了,却不知道是哪一处改出来的,说不定还改出了新问题。
改 bug 最稳的办法是:一次只改一处,改完马上运行。报错消失了,说明改对了;换了一个新的报错,也别灰心,说明前一个已经解决了,接着读新的就行。
4、不读报错,整段复制去搜
遇到报错,很多童鞋的第一反应是整段复制去搜。搜索当然有用,可整段 traceback 里有文件路径、行号,每个人的都不一样,直接搜往往搜不到。
真要搜,就搜最后一行:错误类型加说明,比如 ValueError: could not convert string to float 。把引号里你自己的值去掉,搜到的结果会更多。
不过,比起去网上搜,还有一个更靠得住的地方:官方文档。下一节,咱们就去查一次。
七、自己查一次文档
这回咱们碰上一个新的报错,看看怎么自己去查。先给记账本加个小功能:算算平均每笔花了多少钱。要是一笔账都没记,也就是 entries 是空的,会怎么样呢?
entries = []
total = 0
print(f'平均每笔 {total / len(entries):.2f} 元')报错信息的最后一行是:
ZeroDivisionError: division by zeroZeroDivisionError ,这个错误前面还没见过。遇到没见过的报错,怎么办?
Python 官方文档里,有一页专门列出了所有内置的错误类型,叫「内置异常」。这回咱们直接去查:内置异常 ZeroDivisionError 。
找到 ZeroDivisionError 那一条,文档是这么说的:当除法或取余运算的第二个参数为零时将被引发。
这句话里的「第二个参数」,指的就是除数。再对照一下代码:total / len(entries) 里,除数是 len(entries) 。一笔账都没记的时候,len(entries) 是 0,除数也就是 0 了。
知道了原因,改法也就有了:先判断有没有记账,再算平均:
entries = []
total = 0
if len(entries) == 0:
print('今天还没有记账')
else:
print(f'平均每笔 {total / len(entries):.2f} 元')输出的结果:
今天还没有记账一个报错查明白了,现在轮到你自己动手:在同一页文档里,再找到 IndexError 和 KeyError ,读读它们的说明。然后想一想,记账本里哪些地方可能会碰到它们?
想好了再往下看。
IndexError:用位置去取entries里的某一笔时,位置超出了列表的长度,就会碰到它。KeyError:按类别汇总时,第一次遇到某个类别,就直接用totals[category]去取,也会碰到它。实战 2 就踩过这个坑。
以后遇到没见过的报错,就这么办:先看最后一行的错误类型,再去「内置异常」这一页查它的意思,最后回到代码里,找出是哪个值让它出了错。
这一章的本事都学全了,下一节把它们用到记账本上。
八、记账本 v1.1
现在,把这一章学到的本事都用到记账本上。在实战 2 的记账本 v1 的基础上,主要有这三处改进:
- 金额用
read_cost()来读,输错了会请你重新输入,不会再崩。 - 名称和类别都用
.strip()去掉前后的空格。 - 一笔都没记的时候,不算平均,免得除以 0。
def read_cost():
while True:
text = input('花了多少钱:').strip()
if text.replace('.', '', 1).isdigit():
return float(text)
print('请输入数字,比如 12 或 12.5')
entries = []
print('输入「完」结束记账')
while True:
item = input('买了什么:').strip()
if item == '完':
break
category = input('类别:').strip()
cost = read_cost()
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 len(entries) == 0:
print('今天还没有记账')
else:
print(f'一共 {len(entries)} 笔,合计 {total:.2f} 元,平均每笔 {total / len(entries):.2f} 元')奶茶 餐饮 十二块 12.5 午饭 餐饮 25 完
输出的结果:
输入「完」结束记账
买了什么:奶茶
类别:餐饮
花了多少钱:十二块
请输入数字,比如 12 或 12.5
花了多少钱:12.5
买了什么:午饭
类别:餐饮
花了多少钱:25
买了什么:完
餐饮:37.50 元
一共 2 笔,合计 37.50 元,平均每笔 18.75 元对照输出看一看,这几处改进都起作用了。「十二块」没让程序崩掉,而是请你重新输入了一次。午饭那笔的类别后面多敲了一个空格,也被 .strip() 去掉了,所以两笔都算进了餐饮。
细心的童鞋可能发现了,和 v1 比,每一笔的明细不见了。这是为了让代码短一点,v1.1 先去掉了明细,只留汇总。
记账本 v1.1 比 v1 稳当多了。可你有没有发现,代码越来越长,读起来有点费劲了?读输入、记账、汇总,全挤在一起。这个问题,等学完第 7、8 章,实战 4 会来解决。
先来做本章最后一道题:给记账本 v1.1 算一算,平均每笔花了多少钱。
编辑器里已经写好了 read_cost() 和记账的循环。请接着写最后的汇总:
- 一笔都没记的时候,打印
今天还没有记账,不要去算平均,免得除以 0。 - 记了账的时候,打印
一共 3 笔,合计 41.50 元,平均每笔 13.83 元这样的一行,金额都保留两位小数。
「程序输入」框里预填的输入中,第二笔金额先输错成了「四块」。运行后完整的输出应该是:
买了什么:奶茶
花了多少钱:12.5
买了什么:地铁
花了多少钱:四块
请输入数字,比如 12 或 12.5
花了多少钱:4
买了什么:午饭
花了多少钱:25
买了什么:完
一共 3 笔,合计 41.50 元,平均每笔 13.83 元检查答案时还会用一上来就输入「完」,以及只记一笔的情况。
💡 提示(3 条)
平均每笔是 total / count;count 是 0 的时候会报 ZeroDivisionError,所以要先判断
用 if count == 0: 打印「今天还没有记账」,else: 里再算平均
完整代码:
def read_cost(): while True: text = input('花了多少钱:').strip() if text.replace('.', '', 1).isdigit(): return float(text) print('请输入数字,比如 12 或 12.5') count = 0 total = 0 while True: item = input('买了什么:').strip() if item == '完': break total = total + read_cost() count = count + 1 if count == 0: print('今天还没有记账') else: print(f'一共 {count} 笔,合计 {total:.2f} 元,平均每笔 {total / count:.2f} 元')
九、小结与回看
这一章没学新语法,练的是写程序天天都要用的本事:读懂报错,自己把 bug 找出来。回头梳理一下,学到了这几件事:
- 报错信息从下往上读:最后一行是错误类型和说明,再往上找你自己代码的行号。在网页里,你写的代码叫
main.py。 - 函数里出了错,就顺着 traceback 往上看是谁调用了它,问题常常出在传进去的值。
SyntaxError在运行之前就被发现了,一行都不会执行。最常见的原因是中文标点、漏了冒号、括号没配对。- 用户的输入不能全信:先检查,再转换。
isdigit()加上replace('.', '', 1),能挡住大部分不是数字的输入。 - 不报错的 bug,就把变量打印出来,和期望的结果一项一项对。
f"{x=}"能把看不见的空格也露出来。 - 遇到没见过的报错,去官方文档的「内置异常」那一页查。
- 一次只改一处,改完马上运行。
哪里卡住了,就回去看一看:
- 函数的定义、参数和
return:第 6 章 strip()和f"{x=}":实战 1 的「五、常见坑」和「四、不报错,结果却是错的」KeyError和get():实战 2 的「五、钱都花哪儿了?」
最后说一句:报错是写程序的家常便饭,不是你学得不好的证据。写了很多年代码的人,也天天在看 traceback,区别只是他们读得快一点、找得准一点。这一章练的,就是这个「准」。
接下来的第 7、8 章讲迭代和面向对象。学完之后,实战 4 咱们把记账本从头再写两遍:先拆成几个函数,做成 v2;再写成一个类,做成 v3。
读完了?动手练一遍才算真会。
去闯关模式练习 →