《老鸟python 系列》视频上线了,全网稀缺资源,涵盖python人工智能教程,爬虫教程,web教程,数据分析教程以及界面库和服务器教程,以及各个方向的主流实用项目,手把手带你从零开始进阶高手之路!点击 链接 查看详情




调试

阅读:227567887    分享到

在码农群体中,有句话叫写代码就是写 bug,项目程序能一次写完并正常运行的概率几乎为 0,当然简单的 demo 除外。程序代码总会有各种各样的 bug 需要修正,有些 bug 很简单,看看错误信息就知道,有些 bug 很复杂,对于复杂的 bug,我们需要一整套调试程序的手段来找到它。

对于这种排查 bug 的方法有三种主流方式:一种方式是我们用 print 把可能出问题的变量打印到屏幕上,方便我们肉眼看到; 还有一种方式是通过调试工具单步运行,把程序执行动作放慢,我们可以观察每一步程序执行的过程, 这样我们不但能看到变量在内存中的值,还可以看到函数调用堆栈; 第三种方式是输出日志到磁盘文件进行记录,这样对于服务器程序用的比较多, 一个服务程序需要长期运行着,有可能前半年都没有出问题,后面某一天突然出现了问题, 我们不可能在程序运行的一开始就实时的去 print 或者去调试半年,我可以把可能会出现问题的地方做日志记录(输出到磁盘)方便我们在出问题后去查找。

本节课我们就学习以上三种方式,重点我们学习调试方式。

print 打印方式调试

假定有一道题:写一个函数,查找给定 mylist 中的最大值,并返回一个 dict,返回值的样式为:{"max": max}。 下面是给出我们一开始写出的实现代码。

'''
算法思想:
1.先假定 mylist 中第一个元素为最大值,并把该值存储在存放最大值的变量中。
2.遍历 mylist,取出的元素和存储最大值的变量作比较,如果该元素大于存储最大值的变量,则把该元素赋值给存储最大值的变量。
3.一直遍历到最后一个元素,并返回 maxdict。
'''

def findmax(mylist):
    max = mylist[0]  # 先假定第一个元素是最大值
    maxdict = {"max": max}

    for item in mylist:
        if item > max:
            max = item

    return maxdict


mylist = [4, 8, 9, 3]
print(findmax(mylist))

最后我们发现打印出的值为:{"max": 4},这是怎么回事呢,我们程序的语法虽然没有什么问题, 就有可能是我们程序逻辑的问题了,这个时候我们可能会想,是不是我们的算法有问题,我们就会想到,在循环中每次打印出 max 的具体值,来查找程序所出现的问题。

def findmax(mylist):
    max = mylist[0]  # 先假定第一个元素是最大值
    maxdict = {"max": max}

    for item in mylist:
        if item > max:
            max = item
            print(max)      # 8,9
            print(maxdict)  # {'max': 4},{'max': 4}

    return maxdict


mylist = [4, 8, 9, 3]
print(findmax(mylist))

通过打印,我们发现我们的算法没有问题,max 变量每次在循环内判断语句后的值都符合逻辑,但是 maxdict 的值并没有变化,由此我们知道,maxdict 的 "max" 键对应的值 max 和我们变量 max 不在同一块内存。 通过我们的思考和回忆,我们终于迷雾顿开,这个问题可以变化为:a = 4,b = a,a = 8, b 还是 4,a的变化并没有改变 b 的值一个道理。我们把代码修改成如下,正确解决 bug。

def findmax(mylist):
    max = mylist[0]  # 先假定第一个元素是最大值
    maxdict = {"max": max}

    for item in mylist:
        if item > maxdict["max"]:
            maxdict["max"] = item

    return maxdict


mylist = [4, 8, 9, 3]
print(findmax(mylist))

下断点调试

通过 print 方式来查找程序的 bug,并不是很直观,特别是在程序比较复杂的时候,函数调用层次比较深, 循环次数比较多等等。如果有一种方法可以看到程序每一步执行的顺序,变量在内存中的值,以及函数调用的堆栈,这样程序执行的逻辑就可以在视觉上清晰的反馈给码农, 这种查找程序问题的方式叫调试,Pycharm IDE 集成了调试器,我们可以很方便的对程序进行调试。 调试的第一步就是下断点,我们用鼠标在代码的左侧点击左键就可以下断点,如果下图。

下好断点后,我们就可以对程序进行调试了,调试方法:在程序文件中右键,然后点击 debug文件名按钮。

进入调试状态后,我们现在开始点击 Run to Cursor按钮,解释器就会继续执行到它应该执行的地方停下来, 我们可以让程序在任意断点处停下来,来观察当前每个变量中的内存值,这样很方便我们排查错误。 下面我们看下调用的函数在循环执行完之后,断点执行到返回值的那一步时的各个变量的内存值,通过调试我们看到 maxdict 中的 max 值和 max 变量值不是同一个值,我们就会想到两个变量不是同一块内存。

到目前为止我们给大家演示了如何使用 Pycharm 自带的调试器对程序下断点,断点调试等功能,当然调试器的功能 远远不止这些,比如我们可以查看函数调用堆栈等等,大家可以在项目开发过程中,熟练使用调试。

打日志调试

大家想想,如果我们的程序在一开始运行时没有出现问题,可能在经过一点时间运行后才出现了莫名其妙的问题, 这个时候我们靠 print 输出和下断点调试是没办法找出问题的。比如我们写的一个服务器程序,跑了半年都没有出现问题, 在半年后的某一天出现了问题,我们不可能用 print 打印半年吧,也不可能人为的下断点调试半年。 此时我们可以使用打日志的方式。

import logging
import time

logging.basicConfig(filename='mylog.log',
format='[%(asctime)s-%(filename)s-%(levelname)s:%(message)s]',
level=logging.DEBUG, filemode='a', datefmt='%Y-%m-%d%I:%M:%S %p')

def reciprocal(data):
    for item in data:
        time.sleep(1)   
        try:
            print("%f" % (1 / item))
        except:
            logging.error("出现错误,此时除数为:" + str(item))

mylist = [2.0, 9.0, 0.0, 1.0, 5.0]  # 假定这写是用户给的做除数的数据
reciprocal(mylist)                  # 我们求倒数

本节重要知识点

会使用 Pycharm 自带的调试功能。

养成使用调试技能找程序 bug 的习惯。

作业

写一个类,里面有属性和函数,类外面定义对象,然后调用函数,调试一遍,看看运行运行的过程。


如果以上内容对您有帮助,请老板用微信扫一下赞赏码,赞赏后加微信号 birdpython 领取免费视频。


登录后评论

user_image
纯纯
2020年11月5日 01:06 回复

不能代替pdb进行动态调试的工具都不好用


user_image
李国宝
2020年8月11日 09:25 回复

目前写的脚本只允许我用print调试[飙泪笑]


user_image
JeAnine9ann
2020年6月16日 11:46 回复

pycharm是最好的调试方式


user_image
轮带逛
2019年10月10日 23:21 回复

同问!请问现在哪款python debug工具可以显示大矩阵的变量。像excel那样显示!


user_image
木大木大
2019年7月6日 01:13 回复

pycharm vscode等随便哪个IDE的debug都很强吧[思考]