ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

章节十二:编程思维:如何debug

2022-06-13 14:01:13  阅读:234  来源: 互联网

标签:章节 guess 反面 代码 编程 print debug bug 输入


章节十二:编程思维:如何debug

目录

想必在这段学习的过程中,最让你感到沮丧和苦恼的是来自终端的无情报错,那鲜艳的红色预警每次都能让你叹气三连。

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

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

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

image.png-1359.9kB

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

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

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

从此,bug就化身计算机领域里程序故障的代名词,成为程序员一生如影随形的“亲密敌人”。而我们也自然把排除程序故障叫做debug。

image.png-60.7kB

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

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

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

主要将bug分成了四种类型:

image.png-65.6kB

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

1. bug 1:粗心

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

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

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

image.png-124kB

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

image.png-132.7kB

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

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

image.png-130.7kB

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

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

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

image.png-110.2kB

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

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

image.png-119.4kB

你找到bug了么?

image.png-75.4kB

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

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

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

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

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

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

image.png-224.8kB

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

嘿嘿,你成功解锁了吗?

image.png-131.8kB

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

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

image.png-105.2kB

2. bug 2:知识不熟练

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

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

image.png-129.4kB

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

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

print(sunday)

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

image.png-123kB

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

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

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

3. bug 3:思路不清

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

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

针对这一点,我先给大家推荐两个工具:

image.png-68.5kB

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

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

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

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

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

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

print()函数常和#号注释结合在一起用来debug。下面来讲一个例子:

题目是长这样的:

image.png-186.5kB

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

image.png-199kB

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

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()函数确定这行的操作有无问题。如果运行这段代码,输入'黄轩',终端会这样显示:

image.png-102.2kB

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

image.png-193.3kB

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

image.png-91.3kB

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

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这个变量如果不在['正面','反面']这个列表中,就开始循环”。

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

image.png-287.9kB

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

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

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('大失败!')

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

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

image.png-192.2kB

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

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('大失败!')

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

image.png-385.5kB

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

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

image.png-318.3kB

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

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——“被动掉坑”。

4. bug 4:被动掉坑

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

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

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

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

image.png-155.6kB

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

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

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

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

其实,这个思路是行不通的。

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

image.png-110.1kB

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

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

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

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

image.png-78.6kB

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

image.png-149.6kB

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

image.png-120.8kB

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

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

image.png-221.6kB

while True:
    try:
        age = int(input('你今年几岁了?'))
        break
    except ValueError:
        print('你输入的不是数字!')

if age < 18:
    print('不可以喝酒噢')

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

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

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

image.png-124kB

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

image.png-206.1kB

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

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

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

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

image.png-105.2kB

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

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

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

5. 习题练习

5.1 习题一

1.练习介绍:
通过今天的练习,运用课堂上学到的知识:如何给代码debug,慢慢培养debug的习惯和信心。

2.练习要求:
找出下列3个代码的错误,并将其纠正

3.知识点debug
期末考试结束了,小加在教务系统查到了自己的几门必修课分数,他想通过python计算自己的平均分。
于是写了下面的代码,可是总是得不到结果,请帮纠正bug并跑通程序。

image.png-236.5kB

可以这么写:

image.png-195.3kB

也可以这么写:

image.png-207.3kB

备注:一定要分清全局变量和局部变量的区别

4.思维不清debug
小强认识了一个新朋友叫旺财,他想让你给他取个外号,但他很不喜欢别人叫他小狗和汪汪,
于是写了一个程序让自己避免叫他这两个外号中的一个,可是不知为什么叫他小狗程序也没有警告。

image.png-182.3kB

可以这么写:

image.png-180.2kB

备注:误用了while进入了死循环,if条件那里应该是or,而不是and。不可能存在 x等于两个不同的字符串。一定要分清and和or的用法

5.被动掉坑debug
小明想用python写个程序,看看自己的存款每个月涨了多少倍。
可是发现程序报错,你能帮他找出错误,使程序重新运行吗?

image.png-205.4kB

备注:报错是因为除数不能为0。debug的方式是加个条件判断。

5.2 习题二

1.练习介绍:
通过这个练习,我们会用代码做出一个贴心的除法计算器:只要输入有误,就会给出相应的报错信息。

2.练习要求:
这个除法计算器需要包含的报错信息有:输入了非数值(即不属于整数和浮点数)、被除数为零以及变量不存在。
为了让代码可以给出相应的报错信息,我们可以运用课堂中谈到的try...except语句。

3.回顾和升级
我们以课堂中的一个代码为例,回顾并升级对try...except语句的用法。
请你按照下侧代码区的提醒阅读和运行代码,了解带多个except的try结构。

image.png-263.1kB

num = [1,2,0,3,1.5,'6']
for x in num:
    try:  # 尝试执行下列代码
        print (6/x)
    except ZeroDivisionError:
        print('0是不能做除数的!')
    except TypeError:  # 当报错信息为TypeError,执行下面的语句。
        print('被除数必须是整值或浮点数!')

4.动手实操
请你找到右侧的代码可能会出现的几种报错(可运行尝试),然后用带多个except的try结构来解决这些可能出现的输入错误。

image.png-311.2kB

while True:
    print('\n欢迎使用除法计算器!\n')
    try:

        x = input('请你输入被除数:')
        y = input('请你输入除数:')
        z = float(x)/float(y)

    # 下面是 print函数的一种用法,用逗号隔开,可在同一行打印不同类型的数据。
        print(x,'/',y,'=',z)
        break  # 当成功运行一次除法运算后,退出程序。
    except ZeroDivisionError:
        print('0是不能做除数的!')
    except ValueError:
        print('只能输入整数')

节选自风变编程学习笔记:https://www.pypypy.cn/

标签:章节,guess,反面,代码,编程,print,debug,bug,输入
来源: https://www.cnblogs.com/ywb123/p/16370598.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有