使用Pdb调试Python

uwny2454 8年前
   <h2>简单介绍</h2>    <p>Python自带 Pdb库,使用 Pdb调试 Python程序还是很方便的。但是远程调试、多线程,Pdb是搞不定的</p>    <p>本文参考的相关文章如下:</p>    <ul>     <li> <p><a href="/misc/goto?guid=4959676909474403774" rel="nofollow,noindex">《指针和字符串和字符串常量、用gdb来获取非法内存中的内容》</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959676909557272496" rel="nofollow,noindex">《Linux gdb调试器用法全面解析》</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959676909650841081" rel="nofollow,noindex">《用PDB库调试Python程序》</a></p> </li>    </ul>    <h2>用Pdb调试有多种方式</h2>    <p>使用 Pdb调试 Python的程序的方式主要是下面的三种!下面逐一介绍</p>    <h3>命令行加-m参数</h3>    <p>命令行启动目标程序,加上-m参数,这样调用 testPdb.py的话断点就是程序执行的第一行之前</p>    <p>本文接下来重点讲到的实例展示就是使用这种方式进行调试的!</p>    <pre>  <code class="language-python">python -m pdb testPdb.py</code></pre>    <h3>在python交互环境调试</h3>    <pre>  <code class="language-python">>>> import pdb  >>> import testPdb  >>> pdb.run('testPdb.test()')</code></pre>    <h3>代码中插入一段程序</h3>    <p>比较常用的,就是在程序中间插入一段程序,相对于在一般 IDE 里面打上断点然后启动 debug,不过这种方式是 hardcode的</p>    <pre>  <code class="language-python">if __name__ == "__main__":    a = 1    import pdb    pdb.set_trace()    b = 2    c = a + b    print(c)</code></pre>    <p>然后正常运行脚本: <strong>python testPdb.py</strong> 到了 pdb.set_trace()那里就会定下来,然后就可以看到调试的提示符 (Pdb)了</p>    <p>针对上面的这段小程序的调试情况如下:</p>    <p><img src="https://simg.open-open.com/show/d1a5300bf256a252b8716bc4bcf9b791.png"></p>    <h2>准备测试程序</h2>    <p>接下来使用上面介绍的第一种方式来调试 Python程序,以此来介绍 pdb常用的命令,不过在开始之前先要准备好测试的程序代码:</p>    <h3>testFun.py</h3>    <p>这是一个会被主模块调用的子模块,用于测试使用 Pdb调试的时候,是不是可以断点从主模块跟踪进入子模块(后续有说明)</p>    <pre>  <code class="language-python">#!/usr/bin/python  # -*- coding: utf-8 -*-    def add(a, b):      return a + b</code></pre>    <h3>testPdb.py</h3>    <p>这是下面被调试的主模块的代码</p>    <pre>  <code class="language-python">#!/usr/bin/python  # -*- coding: utf-8 -*-    def sub(a, b):      return a - b    if __name__ == "__main__":        print ''      import testFun      i = 0      a = 1      while(i < 100):          a = testFun.add(a, 1)          i = i + 1      print "累加结果:", a      print ""        for letter in 'Pdb':          print "当前字母:", letter      print ""        fruits = ['banana', 'apple', 'mango']      for fruit in fruits:          print "当前水果:", fruit      print ""          ret = 0      for num in range(10, 12):          ret = sub(ret, num)      print '循环结果:', ret      print ""        d = {'abc': 123, 123: "abc"}      for (k,v) in d.items():          print "当前键值对:", k, '-', v      print ""</code></pre>    <h2>总结常用的命令</h2>    <h3>基础命令</h3>    <p>h(elp)命令:会打印当前版本 Pdb可用的命令,如果要查询某个命令,可输入 <strong>h [command]</strong> ,例如 <strong>h l</strong> 查看 list命令</p>    <p><img src="https://simg.open-open.com/show/45180940c7112ddab69c04e4e520a7c6.png"></p>    <p>l(ist)命令:可以列出当前将要运行的代码块</p>    <p><img src="https://simg.open-open.com/show/3e68cb32d9227faacfa589d7d2fa8efe.png"></p>    <h3>断点管理</h3>    <p>b(reak):设置断点</p>    <p>比如 <strong>b 12</strong> 就是在当前脚本的第 9行加上断点</p>    <p>比如 <strong>b sub</strong> 就是在当前脚本的 sub函数定义处加断点</p>    <p>除了可以在当前的脚本中添加断点之外,还可以在当前脚本对其他脚本下断点,以上面用到的代码为例 <strong>b testFun.add</strong> 就可以实现在 testFun.py脚本中的 add函数处加断点</p>    <p>如果只用 <strong>b</strong> 就会显示现有的全部断点</p>    <p><img src="https://simg.open-open.com/show/02ee0ac55f1330e2a79f781f641896d4.png"></p>    <p>condition bpnumber [condition]:设置条件断点,比如 <strong>condition 2 a==0</strong> ,就是在第二个断点出加条件 “a==0”</p>    <p><img src="https://simg.open-open.com/show/c1330528c57eb637136dcadb4e0904bc.png"></p>    <p>cl(ear):删除断点,如果后面带有参数,就是清楚指定的断点;如果不带参数就是清除所有的断点</p>    <p><img src="https://simg.open-open.com/show/ef17c26f76431e667498c591c301896b.png"></p>    <p>disable/enable:禁用/激活断点</p>    <p><img src="https://simg.open-open.com/show/7889f3ff216a92051bcfafd149af5564.png"></p>    <h3>程序逻辑控制</h3>    <p>下面展示的几个命令,需要知道对应的脚本的代码和行号,所以这里先截图展示下面测试需要用到的前几行代码</p>    <p><img src="https://simg.open-open.com/show/2c9fb20182c6e5872fd4612db3ab0ad2.png"></p>    <p>c(ont(inue)),让程序正常运行,直到遇到下一个断点</p>    <p><img src="https://simg.open-open.com/show/7f5debb848f239870dee127fc79b9c30.png"></p>    <p>n(ext),让程序运行下一行,如果当前语句有一个函数调用,用n是不会进入被调用的函数体中的</p>    <p>下图中展示的,当对脚本断点调试到 testFun.add(a, 1)时,继续执行n,并不会进入 testFun.add(a, 1)的函数内部</p>    <p><img src="https://simg.open-open.com/show/cec179f3cce30eca32d6feb8dc0deb48.png"></p>    <p>s(tep),跟n相似,但如果当前有一个函数调用,那么 s会进入被调用的函数体中</p>    <p>下图中展示的,当对脚本断点调试到 testFun.add(a, 1)时,继续执行s,会进入 testFun.add(a, 1)对应的函数定义内部,虽然 testFun.add不是本脚本中定义的函数</p>    <p><img src="https://simg.open-open.com/show/b296417b18bd74c9071ec2eb9b7b3ce7.png"></p>    <p>j(ump),让程序跳转到指定的行数</p>    <p>假如当前所在行是 10,注意:假如执行了 <strong>j 20</strong> 之后,那么相当于程序直接跳到 20行,中间的 11~19行其实就直接跳过去根本没有被执行到,所以如果这段代码中有变量的声明或对象的初始化需要在 20行及之后被用到,那么等到用到的时候就可能导致报错!</p>    <p><img src="https://simg.open-open.com/show/8dbe69e523b52daf0cab4043f11703ae.png"></p>    <h3>打印重要信息</h3>    <p>a(rgs),打印当前函数的参数。比如下图就是展示断点进入到 testFun.add内部之后,打印 testFun.add的参数</p>    <p><img src="https://simg.open-open.com/show/cee5d16ae416eb94d4357c832e290a46.png"></p>    <p>p,打印某个变量</p>    <p><img src="https://simg.open-open.com/show/7cc5b6c766e1f0314bab1a560bdea98e.png"></p>    <h3>退出调试</h3>    <p>q,直接退出调试;或者使用 Ctrl+D的方式退出</p>    <p><img src="https://simg.open-open.com/show/c851839749ff4a223458ad53733f216e.png"></p>    <h2>最后说一句</h2>    <p>上面展示的使用 Pdb调试的过程其实是很简单的,文章中主要通过截图展示运行的效果。如果单纯的看一遍文章,不出意外,会很没有头绪,甚至感觉截图中的命令、输出乱七八糟,但是如果亲自动手跟着走一遍流程,花不了一小时,但是效果绝对极佳!</p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000006628456</p>    <p> </p>