【python基础知识】11.如何debug -常见报错原因及排查思路 - 思维篇

简介: 【python基础知识】11.如何debug -常见报错原因及排查思路 - 思维篇

常见报错原因及排查思路


前言


想必在学习python的过程中,最让你感到沮丧和苦恼的是来自运行代码时候的无情报错,那鲜艳的红色预警每次都能让你叹气三连。

但请相信那句亘古不变的鸡汤——“失败乃成功之母”,大佬都是经历了报错的千锤百炼才有了如今的成就,所以不要因此打消你的学习积极性。

那作为一个过来人,为了让你少走一些弯路,这一关我会教授一些如何处理程序错误的小技巧。在此之前,我想和你讲一位女科学家和一只臭虫的故事。

Bug


故事的主人公是被誉为计算机程序之母的格蕾丝·赫伯(Grace Hopper)。时光回到1947年,当时她正在为下图这个庞然大物编制程序。

1.png这是世界上第一部万用计算机的进化版——马克2号(Mark II)。瞧瞧这庞大的机器,可想而知,格蕾丝不止要做脑力活儿,还得做体力活儿。

有一天,她正在调试程序(就跟我们在电脑上运行代码,看终端有没有报错一样),结果老是出现故障。

层层排查后,她拆开了继电器,结果发现有只飞蛾被夹扁在触点中间,从而“卡”住了机器的运行。揪出来之后,格蕾丝幽默地把这只幺蛾子的尸体贴在了她的工作日志上,并喊它叫bug(臭虫)。

从此,bug就化身计算机领域里程序故障的代名词,成为程序员一生如影随形的“亲密敌人”。

2.png

Debug


Debug听上去很专业,意思其实就是“捉臭虫”,也就是自查和解决代码中的问题。

你是不是经常遇到过写出来的代码有着莫名其妙的问题?也许是电脑报错无法运行程序,也许是虽然程序可以运行,但效果却和预期中很不一样。

俗话说的好,coding五分钟,debug两小时。所以,这一关我们就来玩玩Debug游戏,看看你能不能帮助他们“杀”死这些捣蛋虫吧!

一起动手来Debug


在python中,常见的bug,一般都是由以下几种原因导致的:粗心、知识不熟练、思路不清、被动掉坑

3.png

相信你学完这篇后,之后遇到bug也能面不改色,更容易发现和解决自己代码中的问题。让我们立马开始吧~


bug 1:粗心

23.png

那么首先,我们来看第一种类型:由粗心导致的错误代码:

a = input('请输入密码:')
if a == '123456'
    print('通过')

这段代码的意思是:如果用户输入123456,屏幕上会打印出“通过”。但运行这段代码,终端会报错:

4.png

请找找这段代码的bug在哪,思考一下如何修改程序后让它可以正常运行。


相信眼尖的你能发现,这段代码的问题是少了一个【英文冒号】。

5.png

仔细看报错,其中有3个关键信息。(1)line 2代表这个bug出现在第2行,所以,我们在Debug的时候,可以优先从第2行开始检查。

(2)^代表bug发生的位置,这里指出的位置是第二行末尾。(3)这一行写的是错误类型,SyntaxError指的是语法错误。

一开始可能对错误类型的英文不太熟悉,可以直接复制到百度搜索:

6.png

像这样,通过理解报错信息,我们可以快速定位错误的根源。这种阅读、搜索报错信息的能力,在我们以后独立编写愈来愈复杂的程序时显得尤为重要。


我们再来看一段代码。别看这段代码只有两行,却有3处粗心错误,请你帮忙debug,让它能够顺利运行

for x in range(10):
         print(x)  

你找到bug了么?

7.png

需要特殊说明的是:上面的代码不改缩进不会报错。因为缩进只要结构统一,代码就可以正常运行,但是我们约定俗成是 1 个缩进等于 4 个空格,这不是“语法错误”性质的 bug,而是“不规范”性质的 bug。


这是debug前后的代码,对比一下。

# 这是debug前的代码:
for x in range(10):
     print(x)  
# 这是debug后的代码:
for x in range(10):
    print(x)  

虽然粗略地看起来差别不大,但编写代码的严谨性往往就体现在细微之处。

相信以上对你来说应该是小菜一碟,那让我们继续将挑战的难度提升。

下面这段代码的目的是:用户输入用户名和密码,当用户名为abc且密码为123时,显示登录成功,否则登录失败。用户最多可以尝试输入3次。请你修改代码,让程序能顺利运行。

要提醒你,如果光看代码看不出问题,完全可以运行后查看报错信息,再进行修改。

while n<3:
    username = input("请输入用户名:")
    password = input("请输入密码:")
    if username = 'abc' and password = '123':
        print("登录成功")
        break
    else
        n=n+1
        print("输入有误")
else
    print("你输错了三次,登录失败")

怎么样,找到所有问题了吗,代码能正常运行了吗?

8.png

这里有3处问题:(1)没有定义变量n,就使用n<3 (2)=是赋值,判断两个值是否相等应该用==(3)2处else后面都漏了冒号。


修改后的参考代码是这样的:

n=0
while n<3:
    username = input("请输入用户名:")
    password = input("请输入密码:")
    if username == 'abc' and password == '123':
        print("登录成功")
        break
    else:
        n=n+1
        print("输入有误")
else:
    print("你输错了三次,登录失败")

运行结果:

请输入用户名:123
请输入密码:gkjhk
输入有误
请输入用户名:abc
请输入密码:123
登录成功

开始学编程的时候,因为粗心导致的bug可能所占的比重最大,这里老师也提供一份自检清单,列出一些新手最容易犯的粗心错误,多有意识地注意以后就能少犯了。

9.png

bug 2:知识不熟练

10.png

接下来,我们来看看第二种bug:由于知识不够熟练而引起的错误。


让我们一如既往来找茬,该代码的目的是取出列表中的’星期日’,请修改代码让它能正确运行。

week = ['星期一','星期二','星期三','星期四','星期五','星期六','星期日']
sunday = week[7]
print(sunday)

运行结果:

Traceback (most recent call last):
  File "/home/python-class/classroom/apps-1-id-5cd9765e19bbcf00015547b8/57/main.py", line 2, in <module>
    sunday = week[7]
IndexError: list index out of range

这里的知识错误很明显是:忘记了列表的索引是从0而不是从1开始的。所以,正确的代码应该这样写:

week = ['星期一','星期二','星期三','星期四','星期五','星期六','星期日']
sunday = week[6] # week[6]代表列表“week”中的第7个值
print(sunday)

再来一道题:某同学建了一个空列表a,希望往里面增加3个值,让最后的列表变成 [‘A’,‘B’,‘C’],但写出的代码有误。请你帮忙debug,让它能够顺利运行。

a = []
a = append ('A','B','C')

这里的问题出在append()函数,回顾课堂中append()函数的相关知识,或者搜索“python append”,我们可以知道,并没有a=append(‘A’,‘B’,‘C’) 这种用法。

append()函数是列表的一个方法,要用句点.调用,且append()每次只能接受一个参数,所以正确的写法是这样:

a = []
a.append('A')
a.append('B')
a.append('C')
print(a)

或者写成:

a = []
for i in ('A','B','C'):
    a.append(i)
print(a)

这种bug给我们的启示是:当你发现知识点记不清或者不能确定的时候,就要及时复习或者上网搜索。不要强行写出自己不敢确定的代码,这种情况往往容易出错。

如果对某个基础知识点没有熟练的掌握,随着往后知识广度、深度以及项目难度的增加,很可能会增加大量的理解成本,所以多复习、多练习总是没有错滴。


bug 3:思路不清

11.png

接下来我们要挑战的是“思路不清bug”,解决了这类bug,对于初学者而言,八成的问题都能自己解决了。

思路不清指的是当我们解决比较复杂的问题时,由于我们对细节和实现手段思考得不够清楚,要么导致一步错,步步错;要么虽然没有报错,但是程序没有达到我们想要的效果。

针对这一点,我先给大家推荐两个工具:print和#注释

12.png

print()


先来看print()函数。这是大家一开始就接触的函数。我们对它的功能也非常熟悉了——打印内容在屏幕上。

但其实它也能成为我们检验对错的里程碑:遇到关键步骤时print出来,看是否达到我们所期望的结果,以此来揪出错误的那一步。

#号注释


#号注释我们也学过,计算机是不会执行代码中的#号和其之后的内容的。

print('伊丽莎白')
#print('我属于我自己')

比如这个就只会执行第一行代码,而第二行代码会被计算机视而不见。

因此,当你写的代码总是不对,又弄不明白哪里不对的时候,使用#号把后面的代码注释掉,一步一步运行,可以帮助排除错误。


print()函数常和#号注释结合在一起用来debug。


练习题1


下面来讲一个例子:

13.png

以下是一个同学提交的一段错误代码,大家可以运行看看(记得这里有input()函数,要在终端输入,然后点击enter):

movie = {
    '妖猫传':['黄轩','染谷将太'],
    '无问西东':['章子怡','王力宏','祖峰'],
    '超时空同居':['雷佳音','佟丽娅']
}
name=input('你查询的演员是?')
for i in movie:
    actors=[i]
    print(actors) #使用print() 函数查看操作是否正确。
    if name in actors:
        print(name+'出演了'+i)

你可以体验到,这个程序没有达到题目要求的效果,可是又没有报错。这时就需要我们思考,问题出在哪里呢?

1-7行看不出问题,因为字典的写法挺规范的,没出现“粗心bug”。所以,问题应该出现在for循环下面的语句中。

继续看第8行:这位同学想要用for循环遍历这个字典。第9行:这位同学试图取出字典中的值。(对字典用法熟悉的人可以看出,这不符合语法规范)

但如果他自己不知道怎么回事的话,这时,就可以用注释和print()函数来帮助他看看到底是怎么回事,请看下面的第10-12行代码:

movie = {
    '妖猫传':['黄轩','染谷将太'],
    '无问西东':['章子怡','王力宏','祖峰'],
    '超时空同居':['雷佳音','佟丽娅']
}
name=input('你查询的演员是?')
for i in movie:
    actors=[i]
    print(actors) #使用print() 函数查看操作是否正确。
    #if name in actors:
        #print(name+'出演了'+i)

我们先把11-12行的代码注释掉,也就是在代码前面分别加一个#。(多行注释有两种快捷操作:1、在需要注释的多行代码块前后加一组三引号’‘’ 2、选中代码后使用快捷键操作:Windows快捷键是ctrl+/,Mac为cmd+/,适用于本地编辑器)

我们先执行前面的代码,并且用print()函数确定这行的操作有无问题。如果运行这段代码,输入’黄轩’,终端会这样显示:

14.png

可见这样写取到的全部是字典的键,而非值。这时,就能意识到是这一行出了问题,他可以回看知识点,发现字典的值的取法,然后修改代码。


请你来帮他修改代码吧,回想下要如何取出字典里的值。


参考答案是这样的:

movie = {
    '妖猫传':['黄轩','染谷将太'],
    '无问西东':['章子怡','王力宏','祖峰'],
    '超时空同居':['雷佳音','佟丽娅']
}
name=input('你查询的演员是?')
for i in movie:
    actors=movie[i]
    if name in actors:
        print(name+'出演了'+i)
        break
else:
    print(name+'的电影暂时没有出演,敬请期待!')

小结


通过这次debug,我们掌握了解决思路不清bug的三步法:

15.png

练习题2


打铁要趁热,让我们继续来实操。以下代码是一位学员制作的猜硬币游戏,一共有两次猜的机会。

import random
guess = ''
while guess not in ['正面','反面']:
    print('------猜硬币游戏------')
    print('猜一猜硬币是正面还是反面?')
    guess = input('请输入“正面”或“反面”:')
# 随机抛硬币,0代表正面,1代表反面
toss = random.randint(0,1)
if toss == guess:
    print('猜对了!你真棒')
else:
    print('没猜对,你还有一次机会。')
    guess = input('再输一次“正面”或“反面”:')
    if toss == guess:
        print('你终于猜对了!')
    else:
        print('大失败!')

但是,这位学员可能没有想清楚代码的逻辑,导致这个程序有个致命问题:用户永远都不可能猜得对。那要如何把这段代码修改正确呢?请你来试一试。

因为这个程序不报错,所以就算没解决问题,程序也会运行通过。所以需要你自己判断你修改的逻辑是否正确了。

怎么样,你解决了吗?


现在来讲讲解决的思路:


解决问题的第一步,可以先分析代码的含义。

首先,代码开头导入了random模块,并定义了变量 guess。

import random
guess = ''
while guess not in ['正面','反面']:

第三行的guess = input(‘请输入“正面”或“反面”:’)可以分析出,guess是用来存用户输入的变量。


然后继续往下分析while语句:

import random
guess = ''
while guess not in ['正面','反面']:
    print('------猜硬币游戏------')
    print('猜一猜硬币是正面还是反面?')
    guess = input('请输入“正面”或“反面”:')

这一段语句,首先需要弄明白的是while后面跟的条件【guess not in [‘正面’,‘反面’]】的含义。

因为[‘正面’,‘反面’]是一个列表,guess是一个变量,所以看起来这个条件的意思是“如果guess这个变量如果不在[‘正面’,‘反面’]这个列表中,就开始循环”。

所以,你可以先试着随便输入一些东西,看看这个循环会怎么运行:(当我们输入的信息不是【正面】或【反面】的时候,程序会不停地循环,输入【正面】或【反面】的时候可以结束循环)

import random
guess = ''
while guess not in ['正面','反面']:
    print('------猜硬币游戏------')
    print('猜一猜硬币是正面还是反面?')
    guess = input('请输入“正面”或“反面”:')

运行结果:

------猜硬币游戏------
猜一猜硬币是正面还是反面?
请输入“正面”或“反面”:kk
------猜硬币游戏------
猜一猜硬币是正面还是反面?
请输入“正面”或“反面”:ff
------猜硬币游戏------
猜一猜硬币是正面还是反面?
请输入“正面”或“反面”:正面

果然,当我们输入的信息不是【正面】或【反面】的时候,程序会不停地循环。

那到了这里,程序都没有问题。我们接着看。

16.png接下来第11行代码toss = random.randint(0,1),注释上说,这个代码是随机抛硬币,0代表正面,1代表反面。


为了确定random.randint(0,1)功能无误,我们可以写一段代码,随机产生20个数字,看看效果是否如我们所愿。

# 以下代码无需修改,直接运行即可
import random
for i in range(20):
    toss = random.randint(0,1)
    print(toss)

运行结果:

1
1
1
0
0
0
1
1
0
0
1
0
1
1
0
0
0
1
1
0

行后我们发现,随机产生的20个数字的确要么是0,要么是1。所以我们继续看代码。

17.png

问题应该就出现在后面的条件判断语句了。为了方便发现问题,我们可以加入两个print,把条件判断语句先注释掉,看看guess、toss这两个变量,存起来的是什么东西。(直接运行代码,然后输入【正面】或【反面】)

# 以下代码无需修改,直接运行即可
import random
guess = ''
while guess not in ['正面','反面']:
    print('------猜硬币游戏------')
    print('猜一猜硬币是正面还是反面?')
    guess = input('请输入“正面”或“反面”:')
# 随机抛硬币,0代表正面,1代表反面
toss = random.randint(0,1) 
print(guess)
print(toss)
#if toss == guess:
  #  print('猜对了!你真棒')
#else:
  #  print('没猜对,再给你一次机会。')
   # guess = input('再输一次“正面”或“反面”:')
   # if toss == guess:
    #    print('你终于猜对了!')
    #else:
      #  print('大失败!')

运行结果:

------猜硬币游戏------
猜一猜硬币是正面还是反面?
请输入“正面”或“反面”:正面
正面
0

原来,toss会随机生成0或1,而guess会是“正面”或“反面”,这当然会导致【toss == guess】条件为假!也就是无论怎么猜,条件都不成立。

所以,我们已经找到了代码的bug所在,请你思考一下如何解决这个问题,并修复最终的代码吧!:)

在这里提供两种答案,第一种方法是先创建一个列表:

import random
all = ['正面','反面']
guess = ''
while guess not in all:
    print('------猜硬币游戏------')
    print('猜一猜硬币是正面还是反面?')
    guess = input('请输入“正面”或“反面”:')
toss = all[random.randint(0,1)]
# 随机抛硬币,all[0]取出正面,all[1]取出反面
if toss == guess:
    print('猜对了!你真棒')
else:
    print('没猜对,再给你一次机会。')
    guess = input('再输一次“正面”或“反面”:')
    if toss == guess:
        print('你终于猜对了!')
    else:
        print('大失败!')

第二种方法更为取巧,直接把输入的信息限定为’0’或’1’。

import random
guess = ''
while guess not in [0,1]:
    print('------猜硬币游戏------')
    print('猜一猜硬币是正面还是反面?')
    guess = int(input('“正面”请输入0,“反面”请输入1:'))
    #注意要用int()将字符串类型转换为数字类型
toss = random.randint(0,1)
if toss == guess:
    print('猜对了!你真棒')
else:
    print('没猜对,再给你一次机会。')
    guess = int(input('再输一次(“正面”请输入0,“反面”请输入1):'))
    if toss == guess:
        print('你终于猜对了!')
    else:
        print('大失败!')

相信这个例子能让你感受到如何利用print()和#号注释帮助我们理清解题思路,找到问题所在吧。


那我们来看看最后一种bug——“被动掉坑”。


bug 4:被动掉坑

18.png

被动掉坑,是指有时候你的代码逻辑上并没有错,但可能因为用户的错误操作或者是一些“例外情况”而导致程序崩溃。


我们举个例子,当运行以下代码的时候,如果输入的东西不是整数,则程序一定会报错。

age = int(input('你今年几岁了?'))
if age < 18:
    print('不可以喝酒噢')

当我们输入的不是整数,程序会这样报错:

19.png

这里的“ValueError”的意思是“传入无效的参数”。因为,int()函数只接受数字以及内容为整数的字符串。

当我们输入浮点数的时候,input()函数会返回一个内容为浮点数的字符串,同样不符合int()函数的参数要求。

对于这种“被动掉坑bug”,我们该怎么解决呢?请判断下列思路是否可行:

写个条件判断——用type()函数,判断用户输入的是不是整数(2)写个while循环:如果用户输入的不是整数——让用户重输入。

你可能猜到了,这个思路的确行不通。

为什么这么说呢?你输入一个数字试试就知道了:

#你可以输入1试试。
age = input('你今年几岁了?')
print(age)
print(type(age))

运行结果:

你今年几岁了?1
1
<class 'str'>

可以发现,input()函数默认输出的数据类型是字符串,哪怕你输入的数字1,也会被转化为字符串’1’。所以type()函数并不能帮我们判断输入的到底是不是数字。

到这里,似乎只能强硬提醒用户一定要输入数字了呢?其实不然。

try…except…语句


为了不让一些无关痛痒的小错影响程序的后续执行,Python给我们提供了一种异常处理的机制,可以在异常出现时即时捕获,然后内部消化掉,让程序继续运行。

这就是try…except…语句,具体用法如下:

20.png

让我们举个例子。刚才的报错,可以查到报错类型是“ValueError”

21.png

现在你试试不输入整数(比如输入个abc之类的),看代码是否会报错:

try:
    age = int(input('请输入一个整数:'))
except ValueError:
    print('要输入整数噢')

运行结果:

请输入一个整数:ojo
要输入整数噢

所以,用新学到的知识,来试一试解决之前程序的bug吧:

age = int(input('你今年几岁了?'))
if age < 18:
    print('不可以喝酒噢')

参考答案是这样的:

#不用修改代码,直接运行即可,尝试多输入几次非数字
while True:
  try:
        age = int(input('你今年几岁了?'))
        break
    except ValueError:
        print('你输入的不是数字!')
if age < 18:
    print('不可以喝酒噢')

代码要点有两个:(1)因为不知道用户什么时候才会输入正确,所以设置while循环来接受输入,只要用户输入不是数字就会一直循环,输入了数字就break跳出循环。(2)使用try……except……语句,当用户输错的时候会给予提示。

我们再来看一个例子,下列代码的目的是遍历列表中的数字,依次用6除以他们。你可以运行一下,看看报错类型是什么。

num = [1,2,0,3]
for x in num:
    print (6/x)

运行结果:

3.0
Traceback (most recent call last):
  File "/home/python-class/classroom/apps-1-id-5cd9765e19bbcf00015547b8/2aeac2a0-9762-906f-461b-5e91c156d62f/main.py", line 3, in <module>
    print (6/x)
ZeroDivisionError: division by zero

可见,报错类型是ZeroDivisionError,因为小学数学告诉我们,0是不可以做除数的,所以导致后面的循环无法执行。

这时呢,你可以使用try…except语句来帮助你:如果出现ZeroDivisionError就提醒’0不能做除数’,现在请你尝试把代码补全吧~

补全后的参考代码:

num = [1,2,0,3]
for x in num:
    try:
    #尝试执行下列代码
        print (6/x)
        #使用6除以num中的元素,并打印
    except ZeroDivisionError:
    #除非发生ZeroDivisionError报错,执行下列代码:
        print('0是不能做除数的!')
        #打印“0是不能做除数的!”

运行结果:

6.0
3.0
0是不能做除数的!
2.0

最后,关于Python的所有报错类型,有需要的话可以在这里查阅:https://www.runoob.com/python/python-exceptions.html

总结


好了,我们已经把四种类型的bug和解决方案都介绍了一遍,现在我们稍微回顾一下。

针对粗心造成的bug,有一份自检清单帮助大家检查。

22.png

针对知识点不熟造成的bug,要记得多复习,查阅笔记,针对性地做练习掌握用法。

针对思维不清的bug,要多用print()函数和#注释一步步地排查错误。

针对容易被忽略的例外情况从而被动掉坑的bug,可以用try…except语句让程序顺利运行。

最后,希望大家以后在面对bug的时候能耐心地找到问题的根源,尝试独立思考、排查错误,逐步提升debug的能力。

相关文章
|
1月前
|
存储 缓存 程序员
Python程序员Debug利器,和Print说再见
Python程序员Debug利器,和Print说再见
25 2
|
3月前
|
存储 Shell 程序员
Python 自动化指南(繁琐工作自动化)第二版:一、PYTHON 基础知识
Python 自动化指南(繁琐工作自动化)第二版:一、PYTHON 基础知识
42 0
|
1月前
|
存储 机器学习/深度学习 数据挖掘
Python编程语言:基础知识与实用代码示例
本文将带您走进Python编程世界,介绍Python的基础知识,并通过实用代码示例展示Python的魅力和应用。
35 0
|
10天前
|
存储 文件存储 Python
python基础知识(一)
python基础(一){编码,字符串格式化,数据类型,运算符,推导式(简化生成数据),函数编程,模块}
|
12天前
|
索引 Python
python 格式化、set类型和class类基础知识练习(上)
python 格式化、set类型和class类基础知识练习
34 0
|
13天前
|
Python
python基础知识
【4月更文挑战第15天】python基础知识
23 7
|
1月前
|
Java Python
【python基础知识】python中怎么判断两个字符串是否相等
【python基础知识】python中怎么判断两个字符串是否相等
31 0
|
1月前
|
程序员 Python
【python基础知识】python怎么查看对象的属性
【python基础知识】python怎么查看对象的属性
20 0
|
3月前
|
存储 BI 网络安全
正在等待继续编辑 - Python - 基础知识专题 - 配置文件与日志管理
正在等待继续编辑 - Python - 基础知识专题 - 配置文件与日志管理
22 0