Python扩展学习系列:pdb调试详解

Python拓展之pdb调试

  • 简介
  • 一、进入pdb调试方法
  • 1、通过vscode终端进入
  • 2、cmd终端进入pdb调试模式方法
  • 二、基本操作命令行
  • 1、list
  • (1)list.
  • (2)list start,end
  • 2、longlist
  • 3、next
  • 4、step
  • 5、continue
  • 6、break
  • (1)break+行
  • (2)break+函数名
  • (3)多文件操作
  • (4)查看断点
  • 7、clear
  • 8、disable
  • 9、enable
  • 10、until
  • 11、print
  • 12、pretty-print
  • 13、where
  • 14、up/down
  • 15、help
  • 16、exit/quit
  • 17、set_trace()
  • 三、调试展示
  • 简介

      我现在用的代码编辑器是vscode,再用vscode之后就很少去使用调试了。我觉得可能一开始配置c、c++时有些困难,似乎调试还需要一些配置,还是比较繁琐,成功配置c、c++后,几乎就没再用过调试功能了。
      前几天心血来潮,想用vscode调试python代码,本来以为可能也需要配置,后来查阅了一下,原来不需要,直接通过vscode对python代码调试就可以,而且也十分清晰、明显。在这个过程中呢,我又了解到了另一种python代码调试方式———pdb调试。
      pdb调试方法主要还是通过终端,用命令行来进行调试。我自己比较喜欢通过终端操作,尤其是在使用vscode之后,终端+命令行操作必不可免。因为我觉得这是一种锻炼,对之后学习Linux操作系统的话,命令行操作可能会有些帮助。所以看到pdb调试之后,我也感兴趣地学了学。虽然它不如vscode自带的调试器直观方便,但对我来说是种对在终端操作的练习。
      下面附带个思维导图,是我从xmind上截下来的,不知道是否清晰。

      上面看着有点小,不知道发布之后点开图片放大能不能看清楚,有几处略微错误。

    一、进入pdb调试方法

    1、通过vscode终端进入

      终端中输入命令:python -m pdb test.py
      其中,test.py是你的文件名

      待调试代码我也给出来吧,注意,肯定是有问题的,要不然就不调试了。
      下面是一个筛选2-1000以内素数的方法。代码的整体思路是从2-1000遍历,每遇到一个素数就输出,并存放到列表l之中。下面并不是最优的方法,现在也不用看懂,后续我们调试一下就好了。

    l=[]
    
    for i in range(2,1001):
        if not l:
            l.append(i)
            print(f'{i}是素数')
        else:
            for j in l:
                if i%j==0:
                    continue
                elif j==l[-1]:  
                    print(f'{i}是素数')
                    l.append(i)
    

      结合终端代码和源代码,,我们可以看到,终端第三行的l=[]其实就是源代码的第一行,这表示我们成功进入了pdb调试模式。

    2、cmd终端进入pdb调试模式方法

      为什么又要弄出个普通终端调试呢?因为我觉得,像在蓝桥杯竞赛中,自带的idle也能debug,应该也是通过pdb方式调试的,但是呢,我也不太熟练,到时候如果需要调试的话,我可以直接通过cmd终端进行pdb调试就好了,方法肯定是多样的。

      首先打开cmd终端,这个应该不用多说吧。一遍默认目录是C:\Users\,我们需要切换到存放python代码的盘,有下面两种方式进入。

  • 方法1:
    F:
    cd Python_code\Data_Struct
    python -m pdb test.py

  • 方法2:
    python -m pdb “F:\Python_code\Data_Struct\test.py”

  •   注意:方法1中每个换行都是一个回车,上面两个方法是我自己的存放路径,具体的需要看你们自己的python代码存放到了哪里。


      具体的大家可以看看上面的命令行

    二、基本操作命令行

    1、list

      简写方法为l。
      默认显示11行代码,可以连续使用,显示更多代码。这11行也是有讲究的。其实不说也没问题,但像这些基本的,我知道的话也会说一下的。

      上面第一行的->代表此时程序运行到这里,第六行的B代表我在这里设置了一个断点,知道就行,后续会说的。
      然后我在终端中输入了list,就展示前11行的结果,可以看到展示的是->之后的11行。而如果->运行到中间,再list的话,它展示的就并不是->之后的11行了,而是->上下5行,加上->的一行,一共11行。
      现在我们让程序运行到断点第六行,然后list之后看看是不是上面说的。

      可以看到,就是我们上面说的那种情况。

    (1)list.

      代表返回上一段展示代代码

    (2)list start,end

      自定义展示第start行和第end行。

      上面代码就是我先通过list展示了前11行,然后又通过list 4,5展示了第4、5行,之后又通过list.返回了之前展示的前11行。

    2、longlist

      简写方法为ll
      列出当前函数所有行,可是在我实际运行的时候是将golbal所有行都列出来了,想具体展示某段函数还不太会操作,测试了一下,需要先运行到函数中,然后使用longlist,可以显示完整函数。
      这里涉及到了符合操作,这里先不深入,只给出展示的结果吧。

      我在全局使用ll,并没有单独展示函数get_prime(),而是所有都展示了

      当我给函数添加断点后,并执行到这里时,再使用ll,就会展示完整函数了(不过我记得跟list一样,也会展示11行,所以想展示所以函数的话,可以用list start,end 方法展示完全)

    3、next

      简写方法为n
      执行下一行语句,注意,函数的定义和函数体都是一条完整语句,所以并不会进入函数之中。直接按回车的话,pdb会重复上一条命令,也就是next,所以不需要一直输入next。这个不再进行展示了。

    4、step

      简写方法为s
      进入函数内部进行调试

    5、continue

      简写方法为c
      直接让程序进入下一个断点或者结束运行(没有添加断点的话)

    6、break

      简写方法为b
      主要用来设置断点和查看断点的,下面分别说明一下。

    (1)break+行

      在第某行添加断点

    (2)break+函数名

      在某函数前加断点

    (3)多文件操作

      如果多文件的话(比如导入了某个库,想在某个库中设置断点)可以break 文件名:行 (eg. break test2.py:4)
      这里不过多进行讲解,因为我自己现在是单文件操作,后续多文件的话会进行补充的。

    (4)查看断点

      使用break可以直接看到所有设置过的断点。

    7、clear

      简写方法为c
      清除断点方法clear+断点序号(每设置一个断点,就会有一个断点序号,从1开始的)
      清除所有断点,直接使用clear就行。
      注意,清楚断点的话,break设置的断点序号不会重置。

      上面我进行了一些break、clear操作,可以大体看看。

    8、disable

      disable+断点序号,暂时禁用某断点

    9、enable

      enable+断点序号,启用某断点

    10、until

      until+行号,直接运行到某行

    11、print

      简写方法p
      查看变量值,可以直接打印列表:print([]),使用语法跟python类似(只能说类似,因为是调试中的,应该不会完全一样)使用p的话不需要加()了

    12、pretty-print

      简写方法为pp
      格式化输出,让变量显示地更精准

    13、where

      显示当前函数调用堆栈
      这一般多文件调试的时候会用到,我这里不深入讲解

    14、up/down

      与where连用,切换上一层、下一层调用栈

    15、help

      列出pdb所有命令,help+命令名获取更详细信息

    16、exit/quit

      退出终端

    17、set_trace()

      这是pdb中的一个方法,使用的话需要import pdb,然后使用时直接pdb.sett_trace(),这样代码运行到这里后就直接自动进入调试了。对于一个没写完的代码,想调试的话可以直接加上这个set_trace()进行调试,然后查看变量有无问题。

    三、调试展示

      这个例子不太好,很不直观,不建议大家看,还是自己操作一下好些。后续我自己调试的时候找个set_trace()的例子再进行说明吧。

      这里我会进行之前错误代码的调试,过程基本上是常用的,可以作为参考。

    源代码:

    l=[]
    
    for i in range(2,1001):
        if not l:
            l.append(i)
            print(f'{i}是素数')
        else:
            for j in l:
                if i%j==0:
                    continue
                elif j==l[-1]:  
                    print(f'{i}是素数')
                    l.append(i)
    

      我打算是先next一步一步地走,期间打印一下各个变量,看看哪里有问题。

      如图,i=2时,直接就是素数,不用判断,并且成功添加到列表l中。后面在进行循环的话,就会直接进入else语句了。我们再来分析一下,看看哪里有问题。
      接下来,我先看看i=5时,输出和变量有没有问题,断点设置可以为break 4,i==5 注意,这里的4指的是第四行,一定是for循环内部的某行,而不是for循环那行。后面的是终止条件,当i=5时停止运行。

      此时我们看到,4不是素数,肯定有问题。再看看变量的值是什么。

      (中间输错了一个,不要在意)我上面说过,列表l式用来存储素数的,4不是素数,确还在这里。初步分析else存在问题。接下来打算一步一步使用next,并且一同查看变量变化,看看问题出在了哪里。

      在我逐步分析后发现了问题所在。内层循环的逻辑是,l是存放所有素数的列表,逐个判断i是否能被列表中的数整除,如果能整除,说明不是素数,退出,如果逐渐判断到列表中的最后一个数,还没有退出,说明这就是个素数,并且添加到l中。
      调试时发现,当i=3时,判断它是素数之后,添加到l之中,本来循环应该结束了,又多进行了一次循环,考虑了一下,其实就是l本身是for循环的循环条件(范围)可是又在循环中对l进行append,实时对循环条件用影响,肯定会有问题的。所以这里append后直接break退出循环才对。
      可是不幸的是,还是出问题了。又进行了上面的几步,发现是continue那里出错了,如果取余等于0的话,说明不是素数,应该直接break,不用再判断了。

    作者:巷北夜未央

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python扩展学习系列:pdb调试详解

    发表回复