调试程序

为什么要调试

我们开发的程序运行的时候,经常会发现运行的结果和我们预期的不符。

有的是直接就报错了,比如像这样

image

出现这种问题,我们处理比较简单,去看报错提示,确定我们错误的代码在哪一行。

比如上面,解释器明确告诉我们,错误在 aa.py 文件的 14 行, 而且错误是: str类型的对象和字符串不能使用除号 /

有了这样的错误提示,我们就知道去检查对应代码,很快就可以发现:fee 是个字符串,不能和 headcount 这个整数 进行除法运算。

然后再改正一下,比如把代码变成这样

avgfee = int(fee) / headcount


但是还有的情况,解释器并不报错,只是运行的结果和我们预期的不一样。

我们来看一个例子:

某手机商店,在其官方网站上有API接口,给出每天的个品牌手机销量,API请求地址是

http://www.python3.vip/doc/tutorial/python/code/0016_price

访问这个请求地址,返回的内容如下所示。

其中逗号分隔的列,其含义依次是 : 手机型号,库存,当天销量,价格

三星S8,     110,50,5200
三星Note8,  123,30,6800

华为Mate10, 170,170,4100
华为P10,    167,57,3300

小米 6,     133,81,2200
小米 mix,   173,61,3200

现在我们要获取:当天销售量最高的手机型号。

大家可以先自己想想怎么写代码。

我们来看下面的一段代码

import requests

# 用requests获取销量信息
res = requests.get('http://www.python3.vip/doc/tutorial/python/code/0016_price')
content = res.text


# 下面两个变量记录当前找到的销量最多的手机和卖出数量
# 初识值都是None
mostsoldphone = None
mostsoldcount = None

for info in content.splitlines():
    info = info.strip()
    # 去掉空行
    if not info:
        continue
    
    items = info.split(',')
    # 销量在倒数第二列,获取销量信息
    soldcount =  items[-2]
    # 型号在 第一列,  获取型号信息
    phonetype =  items[0]

    # 如果前面已经有销量最多的手机记录,和当前这款手机销量比较
    if mostsoldphone:
        # 如果当前这款手机销量更高,把它置为最热卖手机
        if mostsoldcount < soldcount:
            mostsoldcount = soldcount
            mostsoldphone = phonetype

    # 如果前面没有有销量最多的手机记录,说明这是第一条记录
    # 暂时先把它置为最热卖手机
    else:
        mostsoldcount = soldcount
        mostsoldphone = phonetype

print(f'最热卖手机是 {mostsoldphone}, 销量是 {mostsoldcount}')

大家根据注释理解一下代码的逻辑。

咋一看应该没有问题吧?

好的,运行一下看看。发现结果如下

最热卖手机是 小米 6, 销量是 81

怎么回事?我们眼睛一看都知道,最热卖的是 华为Mate10 ,卖出了 170部。

怎么算成小米6了?

这时候,解释器并没有报错,说明没有任何Python语法语义上的错误,而是代码处理逻辑有问题。

怎么办?

很多初学者,往往这时候第一反应就是有点懵,不知道该怎么办。

然后,就是反复的看代码。 有时也不能发现代码到底错在哪里。

其实这时候,最有效的方法的就是去 调试程序

所谓调试程序就是:检查程序运行过程中的 一些关键步骤里面变量 ,看看是否正确。从而判断出是哪里代码的问题。



调试程序的方法

IDE的断点调试

最常用的方法就是 使用IDE的调试功能, 在关键代码处 设置断点, 查看关键变量的值。

什么是设置断点?

设置断点就是 设置某些代码行位置,当 程序运行 到这些位置,就会暂停执行。

我们在程序运行过程中, 查看某个变量的值,必须要让运行的程序能停在相应的位置。


下面我们就以Pycharm 为例,看一下。

首先看决定程序运行结果的关键变量和关键代码有哪些

上面这个程序大体分为2 步

  1. 从服务器获取销量信息

  2. 分析信息,获得最热销手机型号

首先第一步必须正确,也就是获取回来的销量信息是否和需求说的一致

所以我们可以在如下代码处设置断点

image

Pycharm要设置断点非常简单,在要设置断点的代码左边边框上,也就是上图的箭头指向的地方,点击一下鼠标就可以了。

设置好了后,就会出现上图所示的一个红点。

接下来,代码编辑界面,右键点击鼠标,选择debug 调试运行程序,如下图所示

image

注意不要选Run, Run是直接运行,不会停留在断点处


过一会儿就会发现,程序已debug模式运行了,并且会停留在刚才设置的断点处 。 如下所示

image

注意,高亮的一行,表示程序执行 暂停在此处,而且此处高亮行 代码 尚未执行

这时,我们想查看变量content的值(因为里面是获取的具体手机销售信息)。 由于当前暂停的一行还没有执行,所以content变量没有得到值,需要我们执行一行代码,把当前行执行结束,content变量才会被赋值。

那么怎样去执行一行代码呢?

IDE 通常有2种方式。

一种是step over, 对应下方圈起来的按钮

image

点击该按钮,IDE就会让当前的程序执行完当前行的代码。 如果该代码里面有函数调用,执行 不会暂停在函数里面 ,而是直接运行完所有的函数里面的代码, 暂停在 下一行代码。



另一种是step into, 对应下方圈起来的按钮

image

点击该按钮,IDE就会让当前的程序执行一步当前行代码。 如果该代码里面有函数调用,执行 就会暂停在函数里面


这里我们可以点击Step Over 的按钮,全部执行完当前行,光标就会停在下一行,同时下方的Variables窗口里面会显示变量content的值,如图所示

image

由于这个字符串比较长,我们可以点击箭头处的view, 就会弹出下面的信息框,显示出完整的变量内容

image

可以看出读出的内容没有错。

那么下一个要检查的关键的变量 在后面,获取每款手机销量,并进行判断的代码中。

所以,可以设置新的断点,如下图所示

image

然后点击下图所示按钮,表示继续调试程序,程序运行就会停在下一个断点处

image


这时候,如果我们仔细观察,要比较的两个变量 mostsoldcount 和 soldcount

image

细心的读者,就会发现这个两个变量的类型都是字符串。

对字符串进行大小的比较 ?? !!!

这就是问题所在:我们忘了把字符串转为整数类型了,导致比较销量大小的时候出现了问题。

这时,就可以结束调试。修改代码,加上转化为整数的操作,如下

    soldcount = int(items[-2])

重新运行一下,发现结果如下

最热卖手机是 华为Mate10, 销量是 170

这下就正确了。


print出关键变量的值

如果我们运行程序的机器上没有安装IDE, 可以简单一些,直接用代码打印出关键变量的值。 检查是否符合我们的预期就可以了。

比如可以在上面的程序加上一些print语句

import requests

# 用requests获取销量信息
res = requests.get('http://www.python3.vip/doc/tutorial/python/code/0016_price')
content = res.text
print(f'得到的字符串内容为:{content}')

# 下面两个变量记录当前找到的销量最多的手机和卖出数量
# 初识值都是None
mostsoldphone = None
mostsoldcount = None

for info in content.splitlines():
    info = info.strip()
    # 去掉空行
    if not info:
        continue

    items = info.split(',')
    # 销量在倒数第二列,获取销量信息
    soldcount = items[-2]
    # 型号在 第一列,  获取型号信息
    phonetype = items[0]

    # 如果前面已经有销量最多的手机记录,和当前这款手机销量比较
    if mostsoldphone:
        # 如果当前这款手机销量更高,把它置为最热卖手机

        print(f'mostsoldcount为:{mostsoldcount}')
        print(f'soldcount为:{soldcount}')
        if mostsoldcount < soldcount:
            print(f'程序判断 {mostsoldcount} < {soldcount}')
            mostsoldcount = soldcount
            mostsoldphone = phonetype
        else:
            print(f'程序判断 {mostsoldcount} > {soldcount}')

    # 如果前面没有有销量最多的手机记录,说明这是第一条记录
    # 暂时先把它置为最热卖手机
    else:
        mostsoldcount = soldcount
        mostsoldphone = phonetype

print(f'最热卖手机是 {mostsoldphone}, 销量是 {mostsoldcount}')

运行结果如下

得到的字符串内容为:三星S8,     110,50,5200
三星Note8,  123,30,6800

华为Mate10, 170,170,4100
华为P10,    167,57,3300

小米 6,     133,81,2200
小米 mix,   173,61,3200
mostsoldcount为:50
soldcount为:30
程序判断 50 > 30
mostsoldcount为:50
soldcount为:170
程序判断 50 > 170
mostsoldcount为:50
soldcount为:57
程序判断 50 < 57
mostsoldcount为:57
soldcount为:81
程序判断 57 < 81
mostsoldcount为:81
soldcount为:61
程序判断 81 > 61
最热卖手机是 小米 6, 销量是 81

分析输出可以发现, 如下地方有明显异常

mostsoldcount为:50
soldcount为:170
程序判断 50 > 170

这时候可以,有些经验的程序员就会怀疑两个变量的类型是否是整数了,检查一下代码就可以发现原来是字符串,而不是整数。

使用哪种方法?

用 IDE 设置断点 和 添加打印语句 都可以调试程序。

那么我们该使用哪种方法呢?

通常在开发的时候,我们使用 IDE设置断点的方法, 因为毕竟比 添加打印语句 方便, 可以方便的查看任意变量 的值, 可以随意添加 断点。

但是有的时候,程序运行的环境没有 IDE, 比如 给用户使用了, 这时,出现问题,可以使用添加print 语句查看关键变量的方法进行调试。

有些软件程序,我们在开发的时候,就应该写代码 把一些关键信息 保存下来, 当然不是打印到屏幕,而是 写入日志文件中。 这样有些问题,有时候我们不需要修改代码,添加调试打印语句,直接看日志文件就能发现问题所在。

上一页 下一页