python调试_python调试技巧 - CSDN
精华内容
参与话题
  • Python入门:使用PyCharm调试Python程序

    千次阅读 2018-11-06 12:25:24
    Python入门:使用PyCharm调试Python程序 面向Python初学者 PyCharm集成运行环境   在了解Python编程之前,我们需要先弄明白如何编写运行代码。所以非常有必要先讲解一下Python的集成开发环境,也就是IDE...

    Python入门:使用PyCharm调试Python程序

    面向Python初学者

    PyCharm集成运行环境 

        在了解Python编程之前,我们需要先弄明白如何编写运行代码。所以非常有必要先讲解一下Python的集成开发环境,也就是IDE(Integrated Development Environment)。PyCharm是一款优秀的开源Python语言集成开发工具。PyCharm能够调试运行程序,另外它还提供了强大的代码提示功能。在PyCharm的下载页面能够指定安装系统选择付费版(Professional)或者免费版(Community)进行安装。付费版的PyCharm提供了更强大的Python服务器后端开发功能。这里我们以windows系统免费版(PyCharm Community)下载安装。我们只对PyCharm的基本功能进行简单概括,详细内容请查阅官方文档。PyCharm下载地址(https://www.jetbrains.com/PyCharm/download/#section=windows)

        和一般的软件安装过程一样,安装PyCharm需要指定安装位置。这里我们强调四个安装选项。如下图所示,32-bit launcher和64-bit launcher勾选后将在桌面创建对应的32位和64位PyCharm应用图标。它们的区别是32位的应用程序内存上限为2^{32}也就是4G。而64位的应用程序内存上限为2^{64}。应用程序的类型是由操作系统决定的。64位的windows操作系统能够兼容运行32位的应用程序,但是32位的windows操作系统不能运行64位的应用程序。勾选关联.py文件后,双击.py后缀的程序文件将启动PyCharm进行编辑。最后,勾选安装PyCharm绑定的JRE将会在PyCharm安装目录下下载JRE运行环境。补充说明一下,PyCharm软件是由JAVA语言实现的,也就是说它需要运行在JRE虚拟运行环境下。如果你不知道什么是JRE,勾选安装JRE后点击下一步即可。当然,你也可以选择自行安装最新版本的JRE。

        安装完了PyCharm后,我们还需要安装Python语言包。等等为什么还要下载Python语言安装包?因为Python最早是由编程爱好者制作的。Python和PyCharm是不同的组织在维护的。我们建议安装Anaconda版本的Python(https://www.anaconda.com/download/),因为它包含了丰富的Python软件包,软件包的版本管理与维护更加方面,内置模块能够建立多版本Python独立环境。登录(https://www.python.org/downloads/windows/)页面选择最新版本的64位Python 3.6离线下载包(Windows x86-64 executable installer)进行下载安装。这里选择安装Python 3.6只是为了更简明的介绍PyCharm然后通过调试Python代码段了解编程。使用Anaconda配置PyCharm的步骤是相似的。另外,使用windows server系统请下载安装vc运行环境集成包后再尝试安装。win7和win10用户一般不用安装。

        安装完Python 3.6软件包后,我们就可以在PyCharm中使用它来指定Python的编译器,然后我们就可以使用它写Python程序了。如下图所示点击创建新项目。

        点击创建新项目后,会弹出项目设置页面。最上面设置项目位置目录信息。下面的对话框有两个单选按钮,上面一个是创建虚拟解释环境,我们将在下一章中介绍这个设置项的意义。现在我们只勾选“Existing Interpreter”单选按钮,然后点击右面的按钮在弹出的对话框中选择我们刚刚安装的Python 3.6目录下的Python.exe文件完成Python解释器的版本设置了。如果你安装的是Anaconda也可以在安装目录中选择对应的Python.exe文件。

        上图中“点击按钮选择Python解释器”说明部分点击"..."按钮后会弹出下图所示的对话框。我们箭头所指的位置点击按钮在文件对话框中指定文件确认Python解释器。

       接下来就进入了PyCharm的主界面,如图所示,鼠标左键点击项目文件夹,鼠标右键弹出菜单,选择New->Python File即完成Python文件的创建。

        这里我们创建了main.py文件。main.py文件除了后缀名为.py之外,它和一般的windows记事本文件没有什么不同。在左侧的项目结构视图中双击main.py文件,我们可以在右边的编辑区域输入语句print('Hello world!'),如此就完成了一个完整的Python程序。它的功能是打印字符串hello world!。

        接着我们在左侧的项目结构视图中选中main.py文件,选中文件后在顶部菜单中选中“Run...”按钮,如下图所示。上面的Run和Debug按钮因为没有设置运行环境所以是无法选中的。

        点击“Run...”按钮后,会弹出下面的对话框。在这个小对话框中,左键按住main右边的白色三角形,滑动到窗口外松开会弹出二级菜单。点击run和debug按钮会按照PyCharm默认设置执行和调试程序。如果程序没有正确运行,只可能是因为Python解释器路径设置错误,我们重新设置即可。马上会介绍这部分内容。但在这之前,我们先简单介绍一下Run输出窗口和Debug输出窗口的功能。

        点击上图二级弹出菜单的Run按钮后会在PyCharm的下面的run分页窗口内看到程序打印了"Hello world!"运行结果如下图所示。从输出信息中,我们还可以看到Python.exe解释器的使用路径和main.py的完整路径。PyCharm通过调用了Python3.6的终端命令完成main.py文件的运行。

        下面我们在main.py文件中输入如下内容,在上图Run分页的右边点击Debug分页。按照下图所示,先鼠标左键点击代码行的空白处设置断点。红色实心圆表示设置了一个Debug调试断点。接着,点击绿色甲壳虫图案的按钮开始调试。Python程序执行的顺序是从上到下。下面的代码定义了一个fun函数。函数内部先打印字符串inner function,然后对变量a、b赋值0、1,最后执行None空语句。定义函数并没有实际运行,函数fun是在print('Hello world!')语句后执行的。程序的最后令c=2,因为在断点后,所以命中断点时,该语句并没有执行。

    def fun():
    	print('inner function')
    	a = 0
    	b = 1
    	None
    print('Hello world!')
    fun()
    c = 2

        运行Debug,程序会在设置的断点位置停止运行。此时的程序状态会在下方的调试窗口中显示。从下面的左图可以看出程序的断点位置被高亮显示,表示命中断点。下方的状态变量查看窗口显示了函数内部定义的变量a、b的值分别为0、1。左图左下角显示了代码调用堆栈,最上层是fun函数的main.py的第5行,堆栈第二元素是main.py的第8行也就是调用函数的外部,再往下的堆栈代码为debug工程代码显示为灰度。点击调用堆栈能够更新左图右侧的状态变量信息。如下右图所示,在Debugger分页右边的Console分页能够显示此时的程序运行输出信息。

     特别强调一下,如下图所示,点击Console分页左下的命令行图标按钮能够开启交互式调试。在交互式调试模式下,可在Console分页输入Python语句,且语句的执行环境与当前调用堆栈的断点执行环境相同。换句话说,在这里执行语句能够访问变量a、b的值0、1。在交互调试命令行中输入语句d=a+b可以创建变量d且值为1。如右下图所示,变量d的值可在Debugger分页的变量查看器中查看。

    灵活的运用Debugger窗口查看信息能够帮助我们更好的理解Python语言,以及各种软件包的接口函数。PyCharm最下面的分页栏有Python Console如下图所示,它的作用与Python 3.6中的终端环境是相同的。注意这里没有main选项卡。这里的操作和输出结果与项目是独立无关的。在这个窗口可以直接调试运行独立的小代码段。类似的点击右边的Terminal选项卡则显示一个系统相关的终端windows下是Dos命令终端,linux下是bash终端。这里的操作和输出结果也是与项目是独立无关的。windows系统图像界面比较完善可以使用图像界面替代大部分终端命令。linux系统终端命令内容较多,这里就不详细介绍了。

    前面提到过,新建的工程因为在设置开始就指定了Python解释器的位置。所以,如下图所示,点击Run按钮main.py能够正确运行。但对于从网上下载的Python项目,我们使用PyCharm指定项目文件夹打开,则需要为项目指定对应的Python解释器。下图点击Edit...按钮会打开项目运行设置页。项目运行设置页如下图右所示,其中3个重要的参数分别是Scripy Path运行脚本的路径也就是运行.py文件的路径、Parameters脚本运行参数和Python interpreter解释器的版本。下图显示的PyCharm默认生成的main.py运行设置。

    若要更改Python interpreter解释器版本,且上图Python interpreter解释器下拉列表中没有指定,可在PyCharm主菜单File->Settings...中打开设置对话框,如下图所示。

    在settings对话框中左侧导航栏选择Project Interpreter,可以设置Python解释器。本例选中了我们之前安装的Python 3.6,下面的列表会显示目前Python已经安装的工具包。如图所示,Python 3.6默认会安装pip和setuptools两个工具包。右侧还会显示它们的当前版本和最新版本号。

    如果你安装的是Anaconda版本的Python,指定Anaconda解释器Pycharm的Settings窗口会显示所有已安装依赖包。显然Anaconda提供了丰富的安装包。

    最后需要强调一下,因为Python语言使用Tab指制表符来确定代码块的位置,需要确保在PyCharm中开启对应的选项。在主菜单File->Settings...中打开设置对话框,选中Editor->Code Style->Python设置打开编辑设置页,如下图所示。勾选输入Tab制表符和智能Tab两个选项即可。

     

    展开全文
  • Python断点调试方法

    万次阅读 2014-12-20 21:11:57
    Python 代码调试技巧 Debug 对于任何开发人员都是一项非常重要的技能,它能够帮助我们准确的定位错误,发现程序中的 bug。python 提供了一系列 debug 的工具和包,可供我们选择。本文将主要阐述如何利用 python ...

    Debug 对于任何开发人员都是一项非常重要的技能,它能够帮助我们准确的定位错误,发现程序中的 bug。python 提供了一系列 debug 的工具和包,可供我们选择。本文将主要阐述如何利用 python debug 相关工具进行 debug。


    • +内容

    使用 pdb 进行调试

    pdb 是 python 自带的一个包,为 python 程序提供了一种交互的源代码调试功能,主要特性包括设置断点、单步调试、进入函数调试、查看当前代码、查看栈片段、动态改变变量的值等。pdb 提供了一些常用的调试命令,详情见表 1。

    表 1. pdb 常用命令
    命令 解释
    break 或 b 设置断点 设置断点
    continue 或 c 继续执行程序
    list 或 l 查看当前行的代码段
    step 或 s 进入函数
    return 或 r 执行代码直到从当前函数返回
    exit 或 q 中止并退出
    next 或 n 执行下一行
    pp 打印变量的值
    help 帮助

    下面结合具体的实例讲述如何使用 pdb 进行调试。

    清单 1. 测试代码示例
    import pdb 
     a = "aaa"
     pdb.set_trace() 
     b = "bbb"
     c = "ccc"
     final = a + b + c 
     print final

    开始调试:直接运行脚本,会停留在 pdb.set_trace() 处,选择 n+enter 可以执行当前的 statement。在第一次按下了 n+enter 之后可以直接按 enter 表示重复执行上一条 debug 命令。

    清单 2. 利用 pdb 调试
    [root@rcc-pok-idg-2255 ~]#  python epdb1.py 
     > /root/epdb1.py(4)?() 
     -> b = "bbb"
     (Pdb) n 
     > /root/epdb1.py(5)?() 
     -> c = "ccc"
     (Pdb) 
     > /root/epdb1.py(6)?() 
     -> final = a + b + c 
     (Pdb) list 
      1     import pdb 
      2     a = "aaa"
      3     pdb.set_trace() 
      4     b = "bbb"
      5     c = "ccc"
      6  -> final = a + b + c 
      7     print final 
     [EOF] 
     (Pdb) 
     [EOF] 
     (Pdb) n 
     > /root/epdb1.py(7)?() 
     -> print final 
     (Pdb)

    退出 debug:使用 quit 或者 q 可以退出当前的 debug,但是 quit 会以一种非常粗鲁的方式退出程序,其结果是直接 crash。

    清单 3. 退出 debug
    [root@rcc-pok-idg-2255 ~]#  python epdb1.py 
     > /root/epdb1.py(4)?() 
     -> b = "bbb"
     (Pdb) n 
     > /root/epdb1.py(5)?() 
     -> c = "ccc"
     (Pdb) q 
     Traceback (most recent call last): 
      File "epdb1.py", line 5, in ? 
        c = "ccc"
      File "epdb1.py", line 5, in ? 
        c = "ccc"
      File "/usr/lib64/python2.4/bdb.py", line 48, in trace_dispatch 
        return self.dispatch_line(frame) 
      File "/usr/lib64/python2.4/bdb.py", line 67, in dispatch_line 
        if self.quitting: raise BdbQuit 
     bdb.BdbQuit

    打印变量的值:如果需要在调试过程中打印变量的值,可以直接使用 p 加上变量名,但是需要注意的是打印仅仅在当前的 statement 已经被执行了之后才能看到具体的值,否则会报 NameError: < exceptions.NameError … ....> 错误。

    清单 4. debug 过程中打印变量
    [root@rcc-pok-idg-2255 ~]#  python epdb1.py 
     > /root/epdb1.py(4)?() 
     -> b = "bbb"
     (Pdb) n 
     > /root/epdb1.py(5)?() 
     -> c = "ccc"
     (Pdb) p b 
    'bbb'
     (Pdb) 
    'bbb'
     (Pdb) n 
     > /root/epdb1.py(6)?() 
     -> final = a + b + c 
     (Pdb) p c 
    'ccc'
     (Pdb) p final 
     *** NameError: <exceptions.NameError instance at 0x1551b710 > 
     (Pdb) n 
     > /root/epdb1.py(7)?() 
     -> print final 
     (Pdb) p final 
    'aaabbbccc'
     (Pdb)

    使用 c 可以停止当前的 debug 使程序继续执行。如果在下面的程序中继续有 set_statement() 的申明,则又会重新进入到 debug 的状态,读者可以在代码 print final 之前再加上 set_trace() 验证。

    清单 5. 停止 debug 继续执行程序
    [root@rcc-pok-idg-2255 ~]#  python epdb1.py 
     > /root/epdb1.py(4)?() 
     -> b = "bbb"
     (Pdb) n 
     > /root/epdb1.py(5)?() 
     -> c = "ccc"
     (Pdb) c 
     aaabbbccc

    显示代码:在 debug 的时候不一定能记住当前的代码块,如要要查看具体的代码块,则可以通过使用 list 或者 l 命令显示。list 会用箭头 -> 指向当前 debug 的语句。

    清单 6. debug 过程中显示代码
    [root@rcc-pok-idg-2255 ~]#  python epdb1.py 
     > /root/epdb1.py(4)?() 
     -> b = "bbb"
     (Pdb) list 
      1     import pdb 
      2     a = "aaa"
      3     pdb.set_trace() 
      4  -> b = "bbb"
      5     c = "ccc"
      6     final = a + b + c 
      7     pdb.set_trace() 
      8     print final 
     [EOF] 
     (Pdb) c 
     > /root/epdb1.py(8)?() 
     -> print final 
     (Pdb) list 
      3     pdb.set_trace() 
      4     b = "bbb"
      5     c = "ccc"
      6     final = a + b + c 
      7     pdb.set_trace() 
      8  -> print final 
     [EOF] 
     (Pdb)

    在使用函数的情况下进行 debug

    清单 7. 使用函数的例子
    import pdb 
     def combine(s1,s2):      # define subroutine combine, which... 
        s3 = s1 + s2 + s1    # sandwiches s2 between copies of s1, ... 
        s3 = '"' + s3 +'"'   # encloses it in double quotes,... 
        return s3            # and returns it. 
     a = "aaa"
     pdb.set_trace() 
     b = "bbb"
     c = "ccc"
     final = combine(a,b) 
     print final

    如果直接使用 n 进行 debug 则到 final=combine(a,b) 这句的时候会将其当做普通的赋值语句处理,进入到 print final。如果想要对函数进行 debug 如何处理呢 ? 可以直接使用 s 进入函数块。函数里面的单步调试与上面的介绍类似。如果不想在函数里单步调试可以在断点处直接按 r 退出到调用的地方。

    清单 8. 对函数进行 debug
    [root@rcc-pok-idg-2255 ~]# python epdb2.py 
     > /root/epdb2.py(10)?() 
     -> b = "bbb"
     (Pdb) n 
     > /root/epdb2.py(11)?() 
     -> c = "ccc"
     (Pdb) n 
     > /root/epdb2.py(12)?() 
     -> final = combine(a,b) 
     (Pdb) s 
     --Call-- 
     > /root/epdb2.py(3)combine() 
     -> def combine(s1,s2):      # define subroutine combine, which... 
     (Pdb) n 
     > /root/epdb2.py(4)combine() 
     -> s3 = s1 + s2 + s1    # sandwiches s2 between copies of s1, ... 
     (Pdb) list 
      1     import pdb 
      2 
      3     def combine(s1,s2):      # define subroutine combine, which... 
      4  ->     s3 = s1 + s2 + s1    # sandwiches s2 between copies of s1, ... 
      5         s3 = '"' + s3 +'"'   # encloses it in double quotes,... 
      6         return s3            # and returns it. 
      7 
      8     a = "aaa"
      9     pdb.set_trace() 
     10     b = "bbb"
     11     c = "ccc"
     (Pdb) n 
     > /root/epdb2.py(5)combine() 
     -> s3 = '"' + s3 +'"'   # encloses it in double quotes,... 
     (Pdb) n 
     > /root/epdb2.py(6)combine() 
     -> return s3            # and returns it. 
     (Pdb) n 
     --Return-- 
     > /root/epdb2.py(6)combine()->'"aaabbbaaa"'
     -> return s3            # and returns it. 
     (Pdb) n 
     > /root/epdb2.py(13)?() 
     -> print final 
     (Pdb)

    在调试的时候动态改变值 。在调试的时候可以动态改变变量的值,具体如下实例。需要注意的是下面有个错误,原因是 b 已经被赋值了,如果想重新改变 b 的赋值,则应该使用! B。

    清单 9. 在调试的时候动态改变值
    [root@rcc-pok-idg-2255 ~]# python epdb2.py 
     > /root/epdb2.py(10)?() 
     -> b = "bbb"
     (Pdb) var = "1234"
     (Pdb) b = "avfe"
     *** The specified object '= "avfe"' is not a function 
     or was not found along sys.path. 
     (Pdb) !b="afdfd"
     (Pdb)

    pdb 调试有个明显的缺陷就是对于多线程,远程调试等支持得不够好,同时没有较为直观的界面显示,不太适合大型的 python 项目。而在较大的 python 项目中,这些调试需求比较常见,因此需要使用更为高级的调试工具。接下来将介绍 PyCharm IDE 的调试方法 .

    使用 PyCharm 进行调试

    PyCharm 是由 JetBrains 打造的一款 Python IDE,具有语法高亮、Project 管理、代码跳转、智能提示、自动完成、单元测试、版本控制等功能,同时提供了对 Django 开发以及 Google App Engine 的支持。分为个人独立版和商业版,需要 license 支持,也可以获取免费的 30 天试用。试用版本的 Pycharm 可以在官网上下载,下载地址为:http://www.jetbrains.com/pycharm/download/index.html。 PyCharm 同时提供了较为完善的调试功能,支持多线程,远程调试等,可以支持断点设置,单步模式,表达式求值,变量查看等一系列功能。PyCharm IDE 的调试窗口布局如图 1 所示。

    图 1. PyCharm IDE 窗口布局
    图片示例

    下面结合实例讲述如何利用 PyCharm 进行多线程调试。具体调试所用的代码实例见清单 10。

    清单 10. PyCharm 调试代码实例
    __author__ = 'zhangying'
     #!/usr/bin/python 
     import thread 
     import time 
     # Define a function for the thread 
     def print_time( threadName, delay): 
        count = 0 
        while count <  5: 
            count += 1 
            print "%s: %s" % ( threadName, time.ctime(time.time()) ) 
     def check_sum(threadName,valueA,valueB): 
        print "to calculate the sum of two number her"
        result=sum(valueA,valueB) 
        print "the result is" ,result; 
     def sum(valueA,valueB): 
        if valueA >0 and valueB>0: 
            return valueA+valueB 
     def readFile(threadName, filename): 
        file = open(filename) 
        for line in file.xreadlines(): 
            print line 
     try: 
        thread.start_new_thread( print_time, ("Thread-1", 2, ) ) 
        thread.start_new_thread( check_sum, ("Thread-2", 4,5, ) ) 
        thread.start_new_thread( readFile, ("Thread-3","test.txt",)) 
     except: 
        print "Error: unable to start thread"
     while 1: 
     # 	 print "end"
        pass

    在调试之前通常需要设置断点,断点可以设置在循环或者条件判断的表达式处或者程序的关键点。设置断点的方法非常简单:在代码编辑框中将光标移动到需要设置断点的行,然后直接按 Ctrl+F8 或者选择菜单"Run"->"Toggle Line Break Point",更为直接的方法是双击代码编辑处左侧边缘,可以看到出现红色的小圆点(如图 2)。当调试开始的时候,当前正在执行的代码会直接显示为蓝色。下图中设置了三个断点,蓝色高亮显示的为正在执行的代码。

    图 2. 断点设置
    图片示例 2

    表达式求值:在调试过程中有的时候需要追踪一些表达式的值来发现程序中的问题,Pycharm 支持表达式求值,可以通过选中该表达式,然后选择“Run”->”Evaluate Expression”,在出现的窗口中直接选择 Evaluate 便可以查看。

    Pychar 同时提供了 Variables 和 Watches 窗口,其中调试步骤中所涉及的具体变量的值可以直接在 variable 一栏中查看。

    图 3. 变量查看
    图片示例 3

    如果要动态的监测某个变量可以直接选中该变量并选择菜单”Run”->”Add Watch”添加到 watches 栏中。当调试进行到该变量所在的语句时,在该窗口中可以直接看到该变量的具体值。

    图 4. 监测变量
    图片示例 4

    对于多线程程序来说,通常会有多个线程,当需要 debug 的断点分别设置在不同线程对应的线程体中的时候,通常需要 IDE 有良好的多线程调试功能的支持。 Pycharm 中在主线程启动子线程的时候会自动产生一个 Dummy 开头的名字的虚拟线程,每一个 frame 对应各自的调试帧。如图 5,本实例中一共有四个线程,其中主线程生成了三个线程,分别为 Dummy-4,Dummy-5,Dummy-6. 其中 Dummy-4 对应线程 1,其余分别对应线程 2 和线程 3。

    图 5. 多线程窗口
    图片示例 5

    当调试进入到各个线程的子程序时,Frame 会自动切换到其所对应的 frame,相应的变量栏中也会显示与该过程对应的相关变量,如图 6,直接控制调试按钮,如 setp in,step over 便可以方便的进行调试。

    图 6. 子线程调试
    图片示例 6

    查看大图

    使用 PyDev 进行调试

    PyDev 是一个开源的的 plugin,它可以方便的和 Eclipse 集成,提供方便强大的调试功能。同时作为一个优秀的 Python IDE 还提供语法错误提示、源代码编辑助手、Quick Outline、Globals Browser、Hierarchy View、运行等强大功能。下面讲述如何将 PyDev 和 Eclipse 集成。在安装 PyDev 之前,需要先安装 Java 1.4 或更高版本、Eclipse 以及 Python。 第一步:启动 Eclipse,在 Eclipse 菜单栏中找到 Help 栏,选择 Help > Install New Software,并选择 Add button,添加 Ptdev 的下载站点 http://pydev.org/updates。选择 PyDev 之后完成余下的步骤便可以安装 PyDev。

    图 7. 安装 PyDev
    图片示例 7

    安装完成之后需要配置 Python 解释器,在 Eclipse 菜单栏中,选择 Window > Preferences > Pydev > Interpreter – Python。Python 安装在 C:\Python27 路径下。单击 New,选择 Python 解释器 python.exe,打开后显示出一个包含很多复选框的窗口,选择需要加入系统 PYTHONPATH 的路径,单击 OK。

    图 8. 配置 PyDev
    图片示例 8

    在配置完 Pydev 之后,可以通过在 Eclipse 菜单栏中,选择 File > New > Project > Pydev >Pydev Project,单击 Next 创建 Python 项目,下面的内容假设 python 项目已经创建,并且有个需要调试的脚本 remote.py(具体内容如下),它是一个登陆到远程机器上去执行一些命令的脚本,在运行的时候需要传入一些参数,下面将详细讲述如何在调试过程中传入参数 .

    清单 11. Pydev 调试示例代码
     #!/usr/bin/env python 
     import os 	 
     def telnetdo(HOST=None, USER=None, PASS=None, COMMAND=None): #define a function 
    	 import telnetlib, sys 
    	 if not HOST: 
    		 try: 
    			 HOST = sys.argv[1] 
    			 USER = sys.argv[2] 
    			 PASS = sys.argv[3] 
    			 COMMAND = sys.argv[4] 
    		 except: 
    			 print "Usage: remote.py host user pass command"
    			 return 
    	 tn = telnetlib.Telnet() # 
    	 try: 
    		 tn.open(HOST) 
    	 except: 
    		 print "Cannot open host"
    		 return 
    	 tn.read_until("login:") 
    	 tn.write(USER + '\n') 
    	 if PASS: 
    		 tn.read_until("Password:") 
    		 tn.write(PASS + '\n') 
    		 tn.write(COMMAND + '\n') 
    		 tn.write("exit\n") 
    		 tmp = tn.read_all() 
    		 tn.close() 
    		 del tn 
    		 return tmp 
    		
     if __name__ == '__main__': 
    	 print telnetdo()

    在调试的时候有些情况需要传入一些参数,在调试之前需要进行相应的配置以便接收所需要的参数,选择需要调试的程序(本例 remote.py),该脚本在 debug 的过程中需要输入四个参数:host,user,password 以及命令。在 eclipse 的工程目录下选择需要 debug 的程序,单击右键,选择“Debug As”->“Debug Configurations”,在 Arguments Tab 页中选择“Variables”。如下 图 9 所示 .

    图 9. 配置变量
    图片示例 9

    在窗口”Select Variable”之后选择“Edit Varuables” ,出现如下窗口,在下图中选择”New” 并在弹出的窗口中输入对应的变量名和值。特别需要注意的是在值的后面一定要有空格,不然所有的参数都会被当做第一个参数读入。

    图 10. 添加具体变量
    图片示例 10

    按照以上方式依次配置完所有参数,然后在”select variable“窗口中安装参数所需要的顺序依次选择对应的变量。配置完成之后状态如下图 11 所示。

    图 11. 完成配置
    图片示例 11

    选择 Debug 便可以开始程序的调试,调试方法与 eclipse 内置的调试功能的使用相似,并且支持多线程的 debug,这方面的文章已经有很多,读者可以自行搜索阅读,或者参考”使用 Eclipse 平台进行调试“一文。

    使用日志功能达到调试的目的

    日志信息是软件开发过程中进行调试的一种非常有用的方式,特别是在大型软件开发过程需要很多相关人员进行协作的情况下。开发人员通过在代码中加入一些特定的能够记录软件运行过程中的各种事件信息能够有利于甄别代码中存在的问题。这些信息可能包括时间,描述信息以及错误或者异常发生时候的特定上下文信息。 最原始的 debug 方法是通过在代码中嵌入 print 语句,通过输出一些相关的信息来定位程序的问题。但这种方法有一定的缺陷,正常的程序输出和 debug 信息混合在一起,给分析带来一定困难,当程序调试结束不再需要 debug 输出的时候,通常没有很简单的方法将 print 的信息屏蔽掉或者定位到文件。python 中自带的 logging 模块可以比较方便的解决这些问题,它提供日志功能,将 logger 的 level 分为五个级别,可以通过 Logger.setLevel(lvl) 来设置。默认的级别为 warning。

    表 2. 日志的级别
    Level 使用情形
    DEBUG 详细的信息,在追踪问题的时候使用
    INFO 正常的信息
    WARNING 一些不可预见的问题发生,或者将要发生,如磁盘空间低等,但不影响程序的运行
    ERROR 由于某些严重的问题,程序中的一些功能受到影响
    CRITICAL 严重的错误,或者程序本身不能够继续运行

    logging lib 包含 4 个主要对象

    • logger:logger 是程序信息输出的接口。它分散在不同的代码中使得程序可以在运行的时候记录相应的信息,并根据设置的日志级别或 filter 来决定哪些信息需要输出并将这些信息分发到其关联的 handler。常用的方法有 Logger.setLevel(),Logger.addHandler() ,Logger.removeHandler() ,Logger.addFilter() ,Logger.debug(), Logger.info(), Logger.warning(), Logger.error(),getLogger() 等。logger 支持层次继承关系,子 logger 的名称通常是父 logger.name 的方式。如果不创建 logger 的实例,则使用默认的 root logger,通过 logging.getLogger() 或者 logging.getLogger("") 得到 root logger 实例。
    • Handler:Handler 用来处理信息的输出,可以将信息输出到控制台,文件或者网络。可以通过 Logger.addHandler() 来给 logger 对象添加 handler,常用的 handler 有 StreamHandler 和 FileHandler 类。StreamHandler 发送错误信息到流,而 FileHandler 类用于向文件输出日志信息,这两个 handler 定义在 logging 的核心模块中。其他的 hander 定义在 logging.handles 模块中,如 HTTPHandler,SocketHandler。
    • Formatter:Formatter 则决定了 log 信息的格式 , 格式使用类似于 %(< dictionary key >)s 的形式来定义,如'%(asctime)s - %(levelname)s - %(message)s',支持的 key 可以在 python 自带的文档 LogRecord attributes 中查看。
    • Filter:Filter 用来决定哪些信息需要输出。可以被 handler 和 logger 使用,支持层次关系,比如如果设置了 filter 为名称为 A.B 的 logger,则该 logger 和其子 logger 的信息会被输出,如 A.B,A.B.C.
    清单 12. 日志使用示例
    import logging 
     LOG1=logging.getLogger('b.c') 
     LOG2=logging.getLogger('d.e') 
     filehandler = logging.FileHandler('test.log','a') 
     formatter = logging.Formatter('%(name)s %(asctime)s %(levelname)s %(message)s') 
     filehandler.setFormatter(formatter) 
     filter=logging.Filter('b') 
     filehandler.addFilter(filter) 
     LOG1.addHandler(filehandler) 
     LOG2.addHandler(filehandler) 
     LOG1.setLevel(logging.INFO) 
     LOG2.setLevel(logging.DEBUG) 
     LOG1.debug('it is a debug info for log1') 
     LOG1.info('normal infor for log1') 
     LOG1.warning('warning info for log1:b.c') 
     LOG1.error('error info for log1:abcd') 
     LOG1.critical('critical info for log1:not worked') 
     LOG2.debug('debug info for log2') 
     LOG2.info('normal info for log2') 
     LOG2.warning('warning info for log2') 
     LOG2.error('error:b.c') 
     LOG2.critical('critical')

    上例设置了 filter b,则 b.c 为 b 的子 logger,因此满足过滤条件该 logger 相关的日志信息会 被输出,而其他不满足条件的 logger(这里是 d.e)会被过滤掉。

    清单 13. 输出结果
    b.c 2011-11-25 11:07:29,733 INFO normal infor for log1 
     b.c 2011-11-25 11:07:29,733 WARNING warning info for log1:b.c 
     b.c 2011-11-25 11:07:29,733 ERROR error info for log1:abcd 
     b.c 2011-11-25 11:07:29,733 CRITICAL critical info for log1:not worked

    logging 的使用非常简单,同时它是线程安全的,下面结合多线程的例子讲述如何使用 logging 进行 debug。

    清单 14. 多线程使用 logging
    logging.conf 
     [loggers] 
     keys=root,simpleExample 
    
     [handlers] 
     keys=consoleHandler 
    
     [formatters] 
     keys=simpleFormatter 
    
     [logger_root] 
     level=DEBUG 
     handlers=consoleHandler 
    
     [logger_simpleExample] 
     level=DEBUG 
     handlers=consoleHandler 
     qualname=simpleExample 
     propagate=0 
    
     [handler_consoleHandler] 
     class=StreamHandler 
     level=DEBUG 
     formatter=simpleFormatter 
     args=(sys.stdout,) 
    
     [formatter_simpleFormatter] 
     format=%(asctime)s - %(name)s - %(levelname)s - %(message)s 
     datefmt= 
    
     code example: 
     #!/usr/bin/python 
     import thread 
     import time 
     import logging 
     import logging.config 
     logging.config.fileConfig('logging.conf') 
     # create logger 
     logger = logging.getLogger('simpleExample') 
     # Define a function for the thread 
     def print_time( threadName, delay): 
    	 logger.debug('thread 1 call print_time function body') 
    	 count = 0 
    	 logger.debug('count:%s',count)

    总结

    全文介绍了 python 中 debug 的几种不同的方式,包括 pdb 模块、利用 PyDev 和 Eclipse 集成进行调试、PyCharm 以及 Debug 日志进行调试,希望能给相关 python 使用者一点参考。更多关于 python debugger 的资料可以参见参考资料。

    展开全文
  • python 调试大法

    2019-12-07 15:17:08
    我觉得没有什么错误是调试器无法解决的,如果没有,那我再说一遍,如果有,那当我没说 一、抛出异常 可以通过 raise 语句抛出异常,使程序在我们已经知道的缺陷处停下,并进入到 except 语句 raise句法: raise...

    说在前面

    我觉得没有什么错误是调试器无法解决的,如果没有,那我再说一遍,如果有,那当我没说

    一、抛出异常

    可以通过 raise 语句抛出异常,使程序在我们已经知道的缺陷处停下,并进入到 except 语句

    raise句法:

    raise关键字

    调用的异常函数名 ValueError (这个函数必须是异常类或一个实例)

    传递给 ValueError 的字符串,包含有用的出错信息

    >>> raise ValueError('This is a error message')
    Traceback (most recent call last):
      File "<pyshell#24>", line 1, in <module>
        raise ValueError('This is a error message')
    ValueError: This is a error message
    

    然后使用 try…except 语句来对抛出的异常做处理

    通常我们在函数本身中抛出异常,然后在调用该函数的地方使用 try…except 语句处理异常

    '''
    遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
    寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
    '''
    #定义一个简单的int类型的加法器
    def calculator(num1,num2):
        if isintance(num1,int)and isintance(num2,int):
            raise Exception('Symbol must be a int type number.')
        return num1+num2
     
    print('please enter two number:')
    num1=input()
    num2=input()
    #在调用函数的地方使用try语句
    try:
        print(calculator(num1,num2))
    except Exception as err:
        print('发生了一个错误:'+str(err))
    #另一种使用情况
    try:
                print(key)
                return self[key]
            except KeyError:#如果在上面遇见了keyError
                raise AttributeError(r"'%s' don't have attribute '%s'"%#就抛出这个AttributeError类型的错误,顺序别弄错<br>(self.__class__.name,key))
    

    注意上面的 as 语句取得 str ,如果不取也是可以的
      
    运行示例:

    RESTART: C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/boxPrint.py 
    please enter two number:
    s
    发生了一个错误:name 'isintance' is not defined
    >>>
    

    二、取的反向跟踪的字符串

    当程序运行出现错误时,python会生成一些错误信息,这些错误信息被称为“反向跟踪”,它包含了出错信息、导致该错误的代码行号,和导致 该错误的函数调用 的 序列,这个序列被称为调用栈。

    只要抛出的异常没有被处理,python就会显示反向跟踪

    以下面程序来展示我们对反向跟踪的解读

    def spam():
        bacon()
    def bacon():
        raise Exception('This is the error message')
     
    spam()
    

    这就是反向跟踪:

    Traceback (most recent call last):
      File "C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/errorExample.py", line 6, in <module>
        spam()
      File "C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/errorExample.py", line 2, in spam
        bacon()
      File "C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/errorExample.py", line 4, in bacon
        raise Exception('This is the error message')
    Exception: This is the error message
    

    我们应该从下往上阅读方向跟踪,通过反向跟踪我们可以知道,这个错误发生在第5行,在bacon函数中;这次特定的bacon调用发生在第2行,spam函数中,而spam函数又是在第6行被调用的。这样,在从多个位置调用函数的程序中,调用栈就能帮助你确定那次调用导致了错误。

    调用 traceback.format_exc() 得到反向跟踪的字符串形式

    前面说过,如果抛出的异常没有被处理,python才会显示反向跟踪。假如我们既想用except处理错误,又想要获得出错信息,就可以用这个函数,需要导入 traceback 模块

    例如,我们可以在程序出现错误时还能继续运行,同时把错误信息记录到日志中。在程序结束后调试程序时,我们就根据日志里记录的信息去调试

    '''
    遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
    寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
    '''
    >>> import traceback
    >>> try:
        raise Exception('This is a error message')
    except:
        errorFile=open('errorInfo.txt','w')
        errorFile.write(traceback.format_exc())  #使用tracback.format_exc()获得反向跟踪的字符串形式
        errorFile.close()
        print('The traceback info was written to errorInfo.txt')
     
         
    112    #返回的是写入的字符个数
    The traceback info was written to errorInfo.txt
    >>>
    

    errorInfo.txt的内容:

    Traceback (most recent call last):
      File "<pyshell#8>", line 2, in <module>
    Exception: This is a error message
    

    三、断言assert语句

    举一个例子。你从学校毕业以后,很久都没有找到工作,有一天你找了一个兼职:宝石大管家。小孩需要拿着与他们身份匹配的标识才能在别处领到宝石,这个标识在你这里领取,你工作做得不错,才做了五分钟就被老板任命为了区域经理,你觉你年纪轻轻就已经成为了二龙山云霄飞车街区的揸Fit人、并且一手建立了二龙山游乐场宝石交易的游戏法则,觉得人生巅峰也不过如此,但是,沉迷于自我陶醉的你根本不知道,你将一个错误的标识给了一个小朋友,导致他没有领到宝石。结果他叫他哥哥来打你了一顿。然后你老板觉得你辜负了他对你的栽培,然后一气之下把你开了,工资当然没有结。最惨的是,你的衣服丢了,当时你为了用肚脐眼上的伤疤吓唬他就把衣服脱了,结果他竟然也有同样的伤疤,然后又被他打了一顿,然后,你的衣服就丢了。你知道这是你最宝贵的财富,因为这是当年女神赠你的礼物,你永远也忘不了毕业那天,在你的寝室楼下,他轻轻的把袋子递给了你,那天你们说了很多,他说感谢你四年来对他的照顾,但是他妈妈不让他谈恋爱,所以让你再等等,你和他一直聊到晚上10点,只为了能当面向他说一句晚安,他很欣赏你的执着,离别之际对你许下了一个承诺:她说假如有一天这件衣服变成了绿色,他一定和你结婚。你知道,这下肯定没有希望了。不仅失去了工作,你失去了爱情。你以为丢了衣服,就再也没机会和他结婚了,万万没想到,最后你们还是成为了夫妻。那天你回来以后就去了网吧,看见旁边的人在写代码,他周围散落的零食包装代表着富有,这一切都被你看在眼里,你知道你看到了希望,然后你就开始学编程了,由于你过人的天赋,没出几十年你就自己创办了一家公司,和阿里啪啪,中国移不动等大公司都建立了不同程度的合作关系,且业务往来十分密切,身边的人都夸你有出息,只是在深夜的时候,你常常想起当年的那个他,你祈求老天再给你一次机会,终于有一天,你qq收到了他的信息,她说要来找你,你在城市最有档次的地方约她吃饭,他一眼就认出来了你,你很开心,你觉得他一点都没变,还是原来的样子,他没有问你衣服的事情,只是不停的向你道歉说是手误当时才把你删了,其实他这些年一直在找你,这次找到你了,就是要和你结婚,你十分激动,但是你强忍着激动的心情,劝他在考虑考虑,他摇了摇头,从他眼神里流露出来的坚定瞬间击垮了你,你再也控制不了自己了,你拿出了那次做兼职留下的宝石钻戒,你一直把它带在身上,就是等着机会到来,他想都没想就一口答应了你的求婚。看到他对你如此信赖,你暗暗发誓一定要用全部的智商去爱她,晚上他非要枕着你的胳膊睡觉,你虽然觉的不舒服但还是让他枕了一夜,你做了一个梦,梦见你们有了自己的孩子,那件衣服也被你找到了
    衣服上还写着“前方高能”几个字,这是你睡得最舒服的一个晚上,你早早就醒来了,发现他也已经起来了,就在床边上坐着,但令你不解的是,看到你睁开了眼睛,他的表情忽然很激动,sua的一声就哭了,等他冷静下来你才知道。原来,你应经昏迷了8年了,8年前,你去买早餐就再也没有回来,你出了车祸,昏迷了8年,留下他和他腹中的孩子。他说这些年他从来没有想过放弃你,他对你的爱帮助他克服了许多困难。如今你醒了,他终于成功了,他高兴的留下了激动的泪水,你也很开心。于是从此以后,你们一家三口过上了幸福的生活。

    “断言”在这个工作流程当中,就是用来检查 你是否把牌发对了 的一个机制。为了避免这样的情况,我们就添加“断言”来检查。

    assert语句包含:

    assert关键字、要判断的条件、逗号、条件为False时显示的字符串

    '''
    遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
    寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
    '''
    >>> podBayDoorStatus='open'  #吊舱门的状态
    >>> assert podBayDoorStatus=='open','podBayDoorStatus需要设置为open'
    #这里结果没有错
    >>> podBayDoorStatus='other content'
    >>> assert podBayDoorStatus=='open','podBayDoorStatus需要设置为open'
    #这里结果出错了
    Traceback (most recent call last):
      File "<pyshell#13>", line 1, in <module>
        assert podBayDoorStatus=='open','podBayDoorStatus需要设置为open'
    AssertionError: podBayDoorStatus需要设置为open
    >>>
    

    我们在程序中为某个变量赋值后,基于 这个变量是这个值 的假定,我们可能写下了大量的代码,即这些代码依赖这个值,才能正确工作。说以我们添加一个断言,确保假定的变量值是对的。

    对于这种情况,我们使用assert让程序立即崩溃就,以减少寻找缺陷的时间,我们不应用 try except 抛出异常,因为这是程序员的错误,而不是用户的错误,对于那些可以恢复的错误(如文件没有找到,用户输入了无效的数据)则应该用抛出异常来处理

    在交通灯模拟中使用断言

    你在编写一个交通信号灯的模拟程序。代表路口信号灯的数据结构是一个字典:

    market_2nd={'ns':'green','ew':'red'}#ns南北向,ew东西向
    

    你希望编写一个函数 switchLight() ,他接受一个路口字典作为参数,并切换红路灯

    你可能认为 switchLight() 只要将每一种灯按顺序切换到下一种颜色: ‘green‘ 值应该切换到 ‘yellow’ , ‘yellow’ 应该切换到 ‘red’ , ‘red’ 应该切换到 ‘green’ 实现这个功能的代码:

    def switchLights(stoplight):
        for key in stoplight.keys():
            if stoplight[key]=='green':
                stoplight[key]='yellow'
            elif stoplight[key]=='yellow':
                stoplight[key]='red'
            elif stoplight[key]=='red':
                stoplight[key]='green'
    

    这样的运行结果:

    >>>
     RESTART: C:\Users\Administrator.SC-201605202132\AppData\Local\Programs\Python\Python37\forTest.py
    {'ns': 'yellow', 'ew': 'green'}
    {'ns': 'red', 'ew': 'yellow'}
    {'ns': 'green', 'ew': 'red'}
    

    你应该发现第一次的输出是错误的,因为南北向和东西向总应该有一个是红色的,如果不是,那么就会出现汽车相撞,为了避免这样的缺陷出现,你应该添加断言

    '''
    遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
    寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
    '''
    market_2nd={'ns':'green','ew':'red'}#ns南北向,ew东西向
     
    def switchLights(stoplight):
        for key in stoplight.keys():
            if stoplight[key]=='green':
                stoplight[key]='yellow'
            elif stoplight[key]=='yellow':
                stoplight[key]='red'
            elif stoplight[key]=='red':
                stoplight[key]='green'
        assert 'red' in stoplight.values(),'交通灯都不是红色的'+str(stoplight)        #在函数里面添加断言
    switchLights(market_2nd)
    print(market_2nd)
    switchLights(market_2nd)
    print(market_2nd)
    switchLights(market_2nd)
    print(market_2nd)
    

    假如你没有看出来这个代码有问题,然后也没有使用断言,当你从运行结果发现问题时,或许要好多时间才能发现问题出现在 stwitchLight 函数中

    禁用断言

    当我们开发测试的时候,我们可以使用断言来帮助我们更早的发现错误,但是程序交付的时候应该是没有缺陷的,这时就不在需要断言了,我们可以在运行python时传入-O选项来禁用断言

    需要从终端窗口运行程序时使用 >>>从终端运行程序<<<

    在这里插入图片描述

    四、日志

    记日志是一种很好的方式,让我们可以理解程序中发生的事,以及事情发生的顺序。python中的 logging 模块让你能很容易的创建自定义的消息记录。这些日志消息列出了你指定的 任何变量 当时的值。缺失日志消息表明有一部分代码被跳过了,从未执行

    4.1使用日志模块

    import logging
    logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
    

    我们使用 logging.debug(‘string’) 来打印日志信息,这个 debug() 函数会调用 basicConfig ,所以我们第二行是指定打印信息的格式

    python记录一个时间的日志时,他会创建一个 logRecord 对象,保存关于该事件的信息。

    logging.debug() 调用不仅打印出了我们传递给他的信息,而且包含时间戳和一个单词DEBUG

    我们以下面的程序为例,展示使用日志来调试程序的大致过程

    '''
    遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
    寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
    '''
    import logging
    logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
    logging.debug('Start of program')
     
    def factorial(n):
        logging.debug('Start of factorial(%s%%)' %(n))  #这里的两个%是什么意思?或许是匹配basicConfig()里format里的后两个参数?
        total=1
        for i in range(n+1):
            total*=i
            logging.debug('i is '+str(i)+', total is '+str(total))
        logging.debug('End of factorial(%s%%)'%(n))
        return total
     
    print(factorial(5))
    logging.debug('End of program')
    

    运行结果:

    RESTART: C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/facatorialLog.py
     2019-03-06 17:39:10,889 - DEBUG - Start of program
     2019-03-06 17:39:10,938 - DEBUG - Start of factorial(5%)
     2019-03-06 17:39:10,973 - DEBUG - i is 0, total is 0
     2019-03-06 17:39:11,001 - DEBUG - i is 1, total is 0
     2019-03-06 17:39:11,030 - DEBUG - i is 2, total is 0
     2019-03-06 17:39:11,058 - DEBUG - i is 3, total is 0
     2019-03-06 17:39:11,083 - DEBUG - i is 4, total is 0
     2019-03-06 17:39:11,108 - DEBUG - i is 5, total is 0
     2019-03-06 17:39:11,132 - DEBUG - End of factorial(5%)
    0
     2019-03-06 17:39:11,187 - DEBUG - End of program
    

    从里面我们可以看到i是从0开始的,这就导致了total变量总是0,当然结果也是0,知道了这些,我们就可以对程序进行改动

    '''
    遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
    寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
    '''
    import logging
    logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
    logging.debug('Start of program')
     
    def factorial(n):
        logging.debug('Start of factorial(%s%%)' %(n))
        #或许是匹配basicConfig()里format里的后两个参数?
        total=1
        for i in range(1,n+1):    #改动在这里
           
    ---snip--
    

    4.2日志级别

    这个级别是全局的

    “日志级别”提供了一种方式,按重要性把日志消息分为了下面5类。这些级别只是一种建议,在工作中,还是有我们自己来为日志消息指定类型。就像上面,我们也可以不用 logging.debug() 而选用其他四种

    python中的日志级别

    级别(上面的是最小的) 日志函数 描述
    DEBUG logging.debug() 最低级别。用于小细节。通常你只有在诊断问题时才需要
    INFO logging.info() 用于记录程序中一般事件的信息,或者是用来确认工作正常
    WARNING logging.warning() 用于表示可能的问题,这些问题不会阻止程序的工作,但将来可能会
    ERROR logging.error() 用于记录错误,它导致程序做某事失败
    CRITICAL logging.critical() 最高级别。用于表示致命的错误,它导致或将要导致程序完全停止工作

    他们显示的格式并区别

    >>> import logging
    >>> logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
    >>> logging.debug('Some debugging details')
     2019-03-06 18:13:44,829 - DEBUG - Some debugging details
    >>> logging.info('The logging is working')
     2019-03-06 18:13:59,984 - INFO - The logging is working
    >>> logging.critical('The program is unable to recover!')
     2019-03-06 18:14:34,237 - CRITICAL - The program is unable to recover!
    >>>
    

    “日志级别”的好处

    “日志级别”的好处在于,你可以改变想看到的 日志消息 的优先级。这通过 basicConfig() 函数的level关键字参数来指定, level=‘logging.DEBUG’ 时会显示所有的日志级别消息, level=‘logging.ERROR’ 时只会显示级别大于等于ERROR的日志消息

    当我们开发了更多程序后,我们可能只会对错误感兴趣,这种情况,就可以通过上面的level参数来设定我们想看到的级别

    4.3禁用日志

    logging.disable() 函数接受一个日志级别,它会禁止该级别和更低级别的所有日志消息,注意这个参数的书写正确

    >>> import logging
    >>> logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
    >>> logging.critical('The program is unable to recover!')
     2019-03-06 18:14:34,237 - CRITICAL - The program is unable to recover!
    >>> logging.disable(logging.CRITICAL)
    >>> logging.critical('The program is unable to recover!')#由于上面的禁用这个就不显示了
    >>>
    

    我们应该吧 logging.disable() 写在程序中接近 import logging 代码行的位置

    4.4将日志记录到文件

    logging.basicConfig() 函数接受 filename 关键字参数,日志消息将被保存到 myProgramLog.txt 文件中,而不会在输出在屏幕上

    >>> import logging
    >>>logging.basicConfig(filename='myProgramlog.txt',level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
    

    4.5 basicConfig 的参数及 logging 模块定义的格式字符串字段

    参数名称 描述
    filename 指定日志输出目标文件的文件名,指定该设置项后日志信心就不会被输出到控制台了
    filemode 指定日志文件的打开模式,默认为’a’。需要注意的是,该选项要在filename指定时才有效
    format 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。
    datefmt 指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效
    level 指定日志器的日志级别
    stream 指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 ValueError异常
    style Python 3.2中新添加的配置项。指定format格式字符串的风格,可取值为’%’、’{‘和’$’,默认为’%’
    handlers Python 3.3中新添加的配置项。该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常。

    五、IDLE的调试器

    "调试器"是IDLE的一项功能,他可以让你每次执行一行代码,并让你清除的查看当前时刻所有变量的值,对于你弄明白程序的问题很有帮助,通过在交互窗口中点击 Debug>Debugger 来打开 调试控制窗口

    5.1窗口上的信息

    调试的时候不要把把断点打到类似while语句上,因为while这样的语句只执行一次,执行多次的是里面包裹的代码,所以单步跳出或者继续的时候就相当于继续执行到这个while语句,结束了要想一次让单步跳出或者继续达到一次执行一轮 while里面代码的效果,就把断点打到while里面

    打开调试窗口后,只要你运行程序调试器就会在第一条指令执行前暂停执行,并显示下面的信息:

    将要执行的代码行;所有局部变量其其值得列表;所有全局变量及其值的列表

    你会发现这里面有多你没有定义的变量,如 __ builtins__ 、 __ doc__ 、 __ file__ ,等等。它们是python在运行程序时,自动设置的变量。这些变量代表的含义我现在也不知道。我们可以只关注那些我们定义的变量。

    程序将保持暂停,知道我们按下调试窗口的5个按钮中的一个:GO、Step、Over、Out、Quit

    Go

    点击Go按钮将导致程序正常执行至终止,或到达一个“断点”(断点稍后会说)。换句话说,如果你完成了调试,希望程序正常继续,就点击Go按钮

    Step

    Step按钮将导致程序执行下一行代码,然后再次暂停。如果下一行代码是一个函数调用,调试器就会“步入”那个函数,调到该函数的第一行。

    Over

    Over按钮将执行下一行代码,与Step按钮类似。但是如果下一行代码是一个函数调用,Over按钮将“跨越”该函数的代码,调试器将在该函数返回后暂停。例如,下一行代码是 print() 调用,而显然我们不关注 print() 这个函数的代码是怎样的工作的,只希望传递给它的字符串打印出来,这时我们就可以使用Over按钮

    Out

    Out按钮将导致调试器全速执行代码行,直到它从当前函数返回。如果你用Step按钮进入了一个函数,现在想要让这个函数全速执行,直到这个函数结束,那么就可以使用Out按钮,让他从当前函数调用中“走出来”

    Quit

    Quit按钮将马上终止该程序,不会执行下面的代码,记住是终止程序,不是终止调试

    5.2关闭调试器

    和打开的操作一样,从交互式窗口点击 Debug>Debugger 就会关闭

    5.3断点

    “断点”可以设置在特定的代码行上,当使用调试器开始调试程序时,按下GO按钮并不会结束程序了,而是会到达断点里暂停。

    我们可以在编辑器里在要设定断点的行右击鼠标,选择 Set Breakpoint ,就在当前行设置了断点,并且会以亮黄色显示,这次我们打开调试器后,再运行程序后按GO按钮就会在这一行停止,当我们要清除断点时,需要在当前行右击鼠标,选择 clear Breakpoint

    当我们想要知道for循环中某一轮中的变量值,我们就可以在那一行设置断点,而不是频繁的点击Over按钮

    '''
    遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
    寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
    '''
    import random
    mark=0
    for i in range(1,1000):
        s=random.randint(0,2)
        if s==1:
            mark+=1
    #我们查看循环到i=500时的mark值就可以在下面设置断点   
        if i==500:
           print('halfway done')    #设置这里为断点,而不要在上一行里设置,因为他是个判断,每一轮都会运行
    print(mark)
    

    断言、异常、日志和调试器,都是在程序中发现错误和预防缺陷的有用工具。用python的断言,是检查自己有没有犯错的好方式。如果必要的条件被我们搞错了,他将会早早的给出警告。断言所针对的错误,是程序不应该尝试恢复的,而是应该让程序立马失败

    异常可以由 try…except 语句捕捉和处理。 logging 模块是一种很好的方式,可以在运行时查看代码的内部,他比使用 pring() 语句要好很多,因为他有不同的日志级别,并能写入日志文件。

    调试器让你每次单步执行一行代码。或者可以用正常的速度运行程序,并让调试器停在你设置的断点的代码行上。利用调试器,你可以看到程序在运行期间,任何时候所有变量的值。

    展开全文
  • Python调试、异常、测试

    万次阅读 2018-03-29 16:05:11
    调试 1.print() 用print()把可能有问题的变量打印出来,但是用print()最大的坏处是将来还得删掉它,想想程序里到处都是print(),运行结果也会包含很多垃圾信息 2.断言 凡是可能有问题的变量,都可以用断言...

    调试

        1.print()

        用print()把可能有问题的变量打印出来,但是用print()最大的坏处是将来还得删掉它,想想程序里到处都是print(),运行结果也会包含很多垃圾信息

        2.断言

        凡是可能有问题的变量,都可以用断言(assert)来替代:

    def foo(s):
        n = int(s)
        assert n != 0, 'n is zero!'
        return 10 / n
    
    def main():
        foo('0')
        assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。

        如果断言失败,assert语句本身就会抛出AssertionError:

    Traceback (most recent call last):
      ...
    AssertionError: n is zero!

        程序中如果到处充斥着assert,和print()相比也好不到哪去。

        3.logging

        和assert比,logging不会抛出错误,而且可以输出到文件:  

    import logging
    logging.basicConfig(level=logging.INFO)
    s = '0'
    n = int(s)
    logging.info('n = %d' % n)
    print(10 / n)
        输出
    $ python3 err.py
    INFO:root:n = 0
    Traceback (most recent call last):
      File "err.py", line 8, in <module>
        print(10 / n)
    ZeroDivisionError: division by zero
        logging允许指定记录信息的级别,有debug,info,warning,error等几个级别,当指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样就可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。

        logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方

        4.pdb

        启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。程序:
    # err.py
    s = '0'
    n = int(s)
    print(10 / n)
        以参数-m pdb启动后,pdb定位到下一步要执行的代码-&gt; s = '0'。
    $ python -m pdb err.py
    > /Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>()
    -> s = '0'
        输入命令l来查看代码, 输入命令n可以单步执行代码;任何时候都可以输入命令p 变量名来查看变量;输入命令q结束调试,退出程序:
    (Pdb) l
      1     # err.py
      2  -> s = '0'
      3     n = int(s)
      4     print(10 / n)
    (Pdb) n
    > /Users/michael/Github/learn-python3/samples/debug/err.py(3)<module>()
    -> n = int(s)
    (Pdb) p s
    '0'
    (Pdb) q

        通过pdb在命令行调试的方法理论上是万能的,但太麻烦了。

        5.pdb.set_trace()

    只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点:
    # err.py
    import pdb
    
    s = '0'
    n = int(s)
    pdb.set_trace() # 运行到这里会自动暂停
    print(10 / n)

        运行代码,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行

        6.IDE集成开发环境

        目前比较好的Python IDE有PyCharm。虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。

    异常

        Python使用被称为异常 的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
        异常是使用try-except 代码块处理的。try-except 代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。使用了try-except 代码块时,即便出现异常,程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的traceback。

    1.使用try-except 代码块

        当你认为可能发生了错误时,可编写一个try-except 代码块来处理可能引发的异常。你让Python尝试运行一些代码,并告诉它如果这些代码引发了指定的异常,该怎么办

        处理ZeroDivisionError 异常的try-except 代码块类似于下面这样:

    try:
        print(5/0)
    except ZeroDivisionError:
        print("You can't divide by zero!")

       1.1try-except-else 代码块

        try-except-else 代码块的工作原理大致如下:Python尝试执行try 代码块中的代码;只有可能引发异常的代码才需要放在try 语句中。有一些仅在try 代码块成功执行时才需要运行的代码;这些代码应放在else 代码块中。except 代码块告诉Python,如果它尝试运行try 代码块中的代码时引发了指定的异常,该怎么办。
    print("Give me two numbers, and I'll divide them.")
    print("Enter 'q' to quit.")
    
    while True:
        first_number = input("\nFirst number: ")
        if first_number == 'q':
            break
        second_number = input("Second number: ")
        try:
            answer = int(first_number) / int(second_number)
        except ZeroDivisionError:
            print("You can't divide by 0!")
        else:
            print(answer)
        通过预测可能发生错误的代码,可编写健壮的程序,它们即便面临无效数据或缺少资源,也能继续运行,从而能够抵御无意的用户错误和恶意的攻击

        1.2FileNotFoundError 异常

        FileNotFoundError 异常,这是Python找不到要打开的文件时创建的异常。在这个示例中,这个错误是函数open() 导致的,因此要处理这个错误,必须将try 语句放在包含open() 的代码行之前:
    filename = 'alice.txt'
    
    try:
        with open(filename) as f_obj:
            contents = f_obj.read()
    except FileNotFoundError:
        msg = "Sorry, the file " + filename + " does not exist."
        print(msg)

        1.3分析文本

        下面来提取童话 Alice in Wonderland 的文本,并尝试计算它包含多少个单词。我们将使用方法split() ,它根据一个字符串创建一个单词列表。下面是对只包含童话名"Alice in Wonderland" 的字符串调用方法split() 的结果:
    >>> title = "Alice in Wonderland"
    >>> title.split()
    ['Alice', 'in', 'Wonderland']
        方法split() 以空格为分隔符将字符串分拆成多个部分,并将这些部分都存储到一个列表中。结果是一个包含字符串中所有单词的列表,虽然有些单词可能包含标点。为计算 Alice in Wonderland 包含多少个单词,我们将对整篇小说调用split() ,再计算得到的列表包含多少个元素,从而确定整篇童话大致包含多少个单词:
    filename = 'alice.txt' 
    try:
        with open(filename) as f_obj:
            contents = f_obj.read()
    except FileNotFoundError:
        msg = "Sorry, the file " + filename + " does not exist."
        print(msg)
    else:
        # 计算文件大致包含多少个单词
        words = contents.split()
        num_words = len(words)
        print("The file " + filename + " has about " + str(num_words) + " words.")

        1.4使用多个文件

        将这个程序的大部分代码移到一个名为count_words() 的函数中,这样对多本书进行分析时将更容易:
    def count_words(filename):
         """计算一个文件大致包含多少个单词"""
        try:
            with open(filename) as f_obj:
                contents = f_obj.read()
        except FileNotFoundError:
            msg = "Sorry, the file " + filename + " does not exist."
            print(msg)
        else:
            # 计算文件大致包含多少个单词
            words = contents.split()
            num_words = len(words)
            print("The file " + filename + " has about " + str(num_words) + " words.")
    
    filename = 'alice.txt'
    count_words(filename)
    filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
    for filename in filenames:
        count_words(filename)
    The file alice.txt has about 29461 words.
    Sorry, the file siddhartha.txt does not exist.
    The file moby_dick.txt has about 215136 words.
    The file little_women.txt has about 189079 words.
      使用try-except 代码块提供了两个重要的优点:避免让用户看到traceback;让程序能够继续分析能够找到的其他文件。如果不捕获因找不到siddhartha.txt而引发的FileNotFoundError 异常,用户将看到完整的traceback,而程序将在尝试分析 Siddhartha 后停止运行——根本不分析 Moby Dick 和 Little Women 。
        Python有一个pass 语句,可在捕获到异常时什么都不要做:
     def count_words(filename):
          """计算一个文件大致包含多少个单词"""
          try:
              --snip--
          except FileNotFoundError:
    ❶         pass
          else:
              --snip--
        出现FileNotFoundError 异常时,将执行except 代码块中的代码,但什么都不会发生。这种错误发生时,不会出现traceback,也没有任何输出。
        pass 语句还可以充当占位符,它提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么

    测试函数

        使用Python模块unittest 中的工具来测试代码。学习编写测试用例,核实一系列输入都将得到预期的输出。你将看到测试通过了是什么样子,测试未通过又是什么样子,还将知道测试未通过如何有助于改进代码。你将学习如何测试函数和类,并将知道该为项目编写多少个测试。

    1单元测试和测试用例

        单元测试 用于核实函数的某个方面没有问题;测试用例 是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。全覆盖式测试 用例包含一整套单元测试,涵盖了各种可能的函数使用方式。对于大型项目,要实现全覆盖可能很难。通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖。

        要学习测试,得有要测试的代码。下面是一个简单的函数,它接受名和姓并返回整洁的姓名:

    name_function.py

    def get_formatted_name(first, last):
        """Generate a neatly formatted full name."""
        full_name = first + ' ' + last
        return full_name.title()

    1能通过的测试

        要为函数编写测试用例,可先导入模块unittest 以及要测试的函数,再创建一个继承unittest.TestCase 的类,并编写一系列方法对函数行为的不同方面进行测试。下面是一个只包含一个方法的测试用例,它检查函数get_formatted_name() 在给定名和姓时能否正确地工作:方法名必须以test_打头,这样它才会在我们运行test_name_function.py时自动运行

    test_name_function.py

    import unittest
    from name_function import get_formatted_name
    
    ❶ class NamesTestCase(unittest.TestCase):    #创建类NamesTestCase继承于unittest.TestCase 类,用于包含针对get_formatted_name()的单元测试
          """测试name_function.py"""
    
          def test_first_last_name(self):
              """能够正确地处理像Janis Joplin这样的姓名吗?"""
    ❷         formatted_name = get_formatted_name('janis', 'joplin')
              #将formatted_name 的值同字符串'Janis Joplin' 进行比较,如果相等,通过;如果不相等,则不通过
    ❸         self.assertEqual(formatted_name, 'Janis Joplin')   
    
    
      unittest.main()

        unittest 类最有用的方法assertEqual() 之一:一个断言 方法。断言方法用来核实得到的结果是否与期望的结果一致。代码行unittest.main() 让Python运行这个文件中的测试。运行test_name_function.py时,得到的输出如下:

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK

        第1行的句点表明有一个测试通过了。接下来的一行指出Python运行了一个测试,消耗的时间不到0.001秒。最后的OK 表明该测试用例中的所有单元测试都通过了。

    2不能通过的测试

        修改get_formatted_name() ,使其能够处理中间名,但这样做让这个函数无法正确地处理像Janis Joplin只有名和姓的姓名。name_function.py

    def get_formatted_name(first, middle, last):
        """生成整洁的姓名"""
        full_name = first + ' ' + middle + ' ' + last
        return full_name.title()
    运行程序test_name_function.py时,输出如下:
    ❶ E    #指出测试用例中有一个单元测试导致了错误
      ======================================================================
    ❷ ERROR: test_first_last_name (__main__.NamesTestCase)  #NamesTestCase 中的test_first_last_name() 导致了错误
      ----------------------------------------------------------------------
    ❸ Traceback (most recent call last):
        File "test_name_function.py", line 8, in test_first_last_name
          formatted_name = get_formatted_name('janis', 'joplin')
      TypeError: get_formatted_name() missing 1 required positional argument: 'last'
    #标准的traceback,它指出函数调用get_formatted_name('janis', 'joplin') 有问题,缺少一个必不可少的位置实参。
      ----------------------------------------------------------------------
    ❹ Ran 1 test in 0.000s
    ❺ FAILED (errors=1) #指出整个测试用例都未通过,因为运行该测试用例时发生了一个错误

    测试未通过时怎么办

        测试未通过时,如果你检查的条件没错,测试通过了意味着函数的行为是对的,而测试未通过意味着你编写的新代码有错。因此,测试未通过时,不要修改测试,而应修复导致测试不能通过的代码:检查刚对函数所做的修改,找出导致函数行为不符合预期的修改。

        将中间名设置为可选的,可在函数定义中将形参middle 移到形参列表末尾,并将其默认值指定为一个空字符串。我们还要添加一个if 测试,以便根据是否提供了中间名相应地创建姓名:

    name_function.py

    def get_formatted_name(first, last, middle=''):
        """生成整洁的姓名"""
        if middle:
            full_name = first + ' ' + middle + ' ' + last
        else:
            full_name = first + ' ' + last
        return full_name.title()
    再次运行test_name_function.py:
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK

    添加新测试

    确定get_formatted_name() 又能正确地处理简单的姓名后,我们再编写一个测试,用于测试包含中间名的姓名。为此,我们在NamesTestCase 类中再添加一个方法:

    import unittest
      from name_function import get_formatted_name
    
      class NamesTestCase(unittest.TestCase):
          """测试name_function.py """
          def test_first_last_name(self):
              """能够正确地处理像Janis Joplin这样的姓名吗?"""
              formatted_name = get_formatted_name('janis', 'joplin')
              self.assertEqual(formatted_name, 'Janis Joplin')
    
          def test_first_last_middle_name(self):
              """能够正确地处理像Wolfgang Amadeus Mozart这样的姓名吗?"""
    ❶         formatted_name = get_formatted_name(
                  'wolfgang', 'mozart', 'amadeus')
              self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')
    
      unittest.main()
    再次运行test_name_function.py时,两个测试都通过了:
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK

    测试类

        1各种断言方法

        Python在unittest.TestCase 类中提供了很多断言方法。断言方法检查你认为应该满足的条件是否确实满足。如果该条件确实满足,你对程序行为的假设就得到了确认,你就可以确信其中没有错误。如果你认为应该满足的条件实际上并不满足,Python将引发异常。

        类的测试与函数的测试相似——你所做的大部分工作都是测试类中方法的行为,但存在一些不同之处,下面来编写一个类进行测试。来看一个帮助管理匿名调查的类:

    survey.py

    class AnonymousSurvey():
          """收集匿名调查问卷的答案"""
    
    ❶     def __init__(self, question):
              """存储一个问题,并为存储答案做准备"""
              self.question = question
              self.responses = []
    
    ❷     def show_question(self):
              """显示调查问卷"""
              print(question)
    
    ❸     def store_response(self, new_response):
              """存储单份调查答卷"""
              self.responses.append(new_response)
    
    ❹     def show_results(self):
              """显示收集到的所有答卷"""
              print("Survey results:")
              for response in responses:
                  print('- ' + response)
    为证明AnonymousSurvey 类能够正确地工作,我们来编写一个使用它的程序:
    from survey import AnonymousSurvey
    
    #定义一个问题,并创建一个表示调查的AnonymousSurvey对象
    question = "What language did you first learn to speak?"
    my_survey = AnonymousSurvey(question)
    
    #显示问题并存储答案
    my_survey.show_question()
    print("Enter 'q' at any time to quit.\n")
    while True:
        response = input("Language: ")
        if response == 'q':
            break
        my_survey.store_response(response)
    
    # 显示调查结果
    print("\nThank you to everyone who participated in the survey!")
    my_survey.show_results()
    输出:
    What language did you first learn to speak?
    Enter 'q' at any time to quit.
    
    Language: English
    Language: Spanish
    Language: English
    Language: q
    
    Thank you to everyone who participated in the survey!
    Survey results:
    - English
    - Spanish
    - English
    

    AnonymousSurvey 类可用于进行简单的匿名调查。假设我们将它放在了模块survey 中,并想进行改进:让每位用户都可输入多个答案;编写一个方法,它只列出不同的答案,并指出每个答案出现了多少次;再编写一个类,用于管理非匿名调查。

    进行上述修改存在风险,可能会影响AnonymousSurvey 类的当前行为。例如,允许每位用户输入多个答案时,可能不小心修改了处理单个答案的方式。要确认在开发这个模块时没有破坏既有行为,可以编写针对这个类的测试。

    test_survey.py

    import unittest
    from survey import AnonymousSurvey
    
    ❶ class TestAnonmyousSurvey(unittest.TestCase):
          """针对AnonymousSurvey类的测试"""
    
    ❷     def test_store_single_response(self):
              """测试单个答案会被妥善地存储"""
              question = "What language did you first learn to speak?"
    ❸         my_survey = AnonymousSurvey(question)
              my_survey.store_response('English')
    
    ❹         self.assertIn('English', my_survey.responses)
    
      unittest.main()
    运行test_survey.py时,测试通过了:
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    OK

    只能收集一个答案的调查用途不大。下面来核实用户提供三个答案时,它们也将被妥善地存储。为此,我们在TestAnonymousSurvey 中再添加一个方法:

     import unittest
     from survey import AnonymousSurvey
    
      class TestAnonymousSurvey(unittest.TestCase):
          """针对AnonymousSurvey类的测试"""
    
          def test_store_single_response(self):
              """测试单个答案会被妥善地存储"""
              --snip--
    
          def test_store_three_responses(self):
              """测试三个答案会被妥善地存储"""
              question = "What language did you first learn to speak?"
              my_survey = AnonymousSurvey(question)
    ❶         responses = ['English', 'Spanish', 'Mandarin']
              for response in responses:
                  my_survey.store_response(response)
    
    ❷         for response in responses:
                  self.assertIn(response, my_survey.responses)
    
      unittest.main()
    运行test_survey.py时,两个测试(针对单个答案的测试和针对三个答案的测试)都通过了:
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK

    方法setUp()与tearDown()

        可以在单元测试中编写两个特殊的setUp()tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。setUp()tearDown()方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码.

        unittest.TestCase 类包含方法setUp() ,让我们只需创建一次实例对象,并在每个测试方法中使用它们。如果你在TestCase 类中包含了方法setUp() ,Python将先运行它,再运行各个以test_打头的方法。这样,在你编写的每个测试方法中都可使用在方法setUp() 中创建的对象了。

        使用setUp() 来创建一个调查对象和一组答案,供方法test_store_single_response() 和test_store_three_responses() 使用:

    import unittest
      from survey import AnonymousSurvey
    
      class TestAnonymousSurvey(unittest.TestCase):
          """针对AnonymousSurvey类的测试"""
    
          def setUp(self):
              """
              创建一个调查对象和一组答案,供使用的测试方法使用
              """
              question = "What language did you first learn to speak?"
    ❶         self.my_survey = AnonymousSurvey(question)
    ❷         self.responses = ['English', 'Spanish', 'Mandarin'] #存储这两样东西的变量名包含前缀self,因此可在这个类的任何地方使用
    
          def test_store_single_response(self):
              """测试单个答案会被妥善地存储"""
              self.my_survey.store_response(self.responses[0])
              self.assertIn(self.responses[0], self.my_survey.responses)
    
          def test_store_three_responses(self):
              """测试三个答案会被妥善地存储"""
              for response in self.responses:
                  self.my_survey.store_response(response)
              for response in self.responses:
                  self.assertIn(response, self.my_survey.responses)
    
      unittest.main()

        再次运行test_survey.py时,这两个测试也都通过了。如果要扩展AnonymousSurvey ,使其允许每位用户输入多个答案,这些测试将很有用。修改代码以接受多个答案后,可运行这些测试,确认存储单个答案或一系列答案的行为未受影响。

        测试自己编写的类时,方法setUp() 让测试方法编写起来更容易:可在setUp() 方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置其属性,这要容易得多。

    注意

      运行测试用例时,每完成一个单元测试,Python都打印一个字符:测试通过时打印一个句点;测试引发错误时打印一个E ;测试导致断言失败时打印一个F。

        参与工作量较大的项目时,你应对自己编写的函数和类的重要行为进行测试。这样你就能够更加确定自己所做的工作不会破坏项目的其他部分,你就能够随心所欲地改进既有代码了。如果不小心破坏了原来的功能,你马上就会知道,从而能够轻松地修复问题。相比于等到不满意的用户报告bug后再采取措施,在测试未通过时采取措施要容易得多。

    练习:对Student类编写单元测试,结果发现测试不通过,请修改Student类,让测试通过:

    class Student(object):
        def __init__(self, name, score):
            self.name = name
            self.score = score
        def get_grade(self):
            if self.score >= 80 and self.score <=100:
                return 'A'
            elif self.score >= 60 and self.score<80:
                return 'B'
            elif self.score>=0 and self.score<60:
                return 'C'
            raise ValueError(r"score must be between 0~100 '%s'" % self.score)
    
    class TestStudent(unittest.TestCase):
    
        def test_80_to_100(self):
            s1 = Student('Bart', 80)
            s2 = Student('Lisa', 100)
            self.assertEqual(s1.get_grade(), 'A')
            self.assertEqual(s2.get_grade(), 'A')
    
        def test_60_to_80(self):
            s1 = Student('Bart', 60)
            s2 = Student('Lisa', 79)
            self.assertEqual(s1.get_grade(), 'B')
            self.assertEqual(s2.get_grade(), 'B')
    
        def test_0_to_60(self):
            s1 = Student('Bart', 0)
            s2 = Student('Lisa', 59)
            self.assertEqual(s1.get_grade(), 'C')
            self.assertEqual(s2.get_grade(), 'C')
    
        def test_invalid(self):
            s1 = Student('Bart', -1)
            s2 = Student('Lisa', 101)
            with self.assertRaises(ValueError):
                s1.get_grade()
            with self.assertRaises(ValueError):
                s2.get_grade()
    
    if __name__ == '__main__':
        unittest.main()
    练习
    class Dict(dict):
    
        def __init__(self, **kw):
            super().__init__(**kw)
    
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
    
        def __setattr__(self, key, value):
            self[key] = value
    import unittest
    
    from mydict import Dict
    
    class TestDict(unittest.TestCase):
    
        def test_init(self):
            d = Dict(a=1, b='test')
            self.assertEqual(d.a, 1)
            self.assertEqual(d.b, 'test')
            self.assertTrue(isinstance(d, dict))
    
        def test_key(self):
            d = Dict()
            d['key'] = 'value'
            self.assertEqual(d.key, 'value')
    
        def test_attr(self):
            d = Dict()
            d.key = 'value'
            self.assertTrue('key' in d)
            self.assertEqual(d['key'], 'value')
    
        def test_keyerror(self):
            d = Dict()
            with self.assertRaises(KeyError):
                value = d['empty']
    
        def test_attrerror(self):
            d = Dict()
            with self.assertRaises(AttributeError):
                value = d.empty

    另一种重要的断言就是期待抛出指定类型的Error,比如通过d['empty']访问不存在的key时,断言会抛出KeyError

    with self.assertRaises(KeyError):
        value = d['empty']
    

    而通过d.empty访问不存在的key时,我们期待抛出AttributeError

    with self.assertRaises(AttributeError):
        value = d.empty

    小结:

    单元测试可以有效地测试某个程序模块的行为,是未来重构代码的信心保证。

    单元测试的测试用例要覆盖常用的输入组合、边界条件和异常。

    单元测试代码要非常简单,如果测试代码太复杂,那么测试代码本身就可能有bug。

    单元测试通过了并不意味着程序就没有bug了,但是不通过程序肯定有bug。

    文件测试

    Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。

    doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用...表示中间一大段烦人的输出。

    让我们用doctest来测试上次编写的Dict类:

    # mydict2.py
    class Dict(dict):
        '''
        Simple dict but also support access as x.y style.
    
        >>> d1 = Dict()
        >>> d1['x'] = 100
        >>> d1.x
        100
        >>> d1.y = 200
        >>> d1['y']
        200
        >>> d2 = Dict(a=1, b=2, c='3')
        >>> d2.c
        '3'
        >>> d2['empty']
        Traceback (most recent call last):
            ...
        KeyError: 'empty'
        >>> d2.empty
        Traceback (most recent call last):
            ...
        AttributeError: 'Dict' object has no attribute 'empty'
        '''
        def __init__(self, **kw):
            super(Dict, self).__init__(**kw)
    
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
    
        def __setattr__(self, key, value):
            self[key] = value
    
    if __name__=='__main__':
        import doctest
        doctest.testmod()
    运行python mydict2.py
    $ python mydict2.py
    什么输出也没有。这说明我们编写的doctest运行都是正确的。如果程序有问题,比如把__getattr__()方法注释掉,再运行就会报错:
    $ python mydict2.py
    **********************************************************************
    File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 10, in __main__.Dict
    Failed example:
        d1.x
    Exception raised:
        Traceback (most recent call last):
          ...
        AttributeError: 'Dict' object has no attribute 'x'
    **********************************************************************
    File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 16, in __main__.Dict
    Failed example:
        d2.c
    Exception raised:
        Traceback (most recent call last):
          ...
        AttributeError: 'Dict' object has no attribute 'c'
    **********************************************************************
    1 items had failures:
       2 of   9 in __main__.Dict
    ***Test Failed*** 2 failures.
    练习:
    def fact(n):
        '''
        Calculate 1*2*...*n
    
        >>> fact(1)
        1
        >>> fact(10)
        3628800
        >>> fact(-1)
        Traceback (most recent call last):
    	...
        ValueError
        '''
        if n < 1:
            raise ValueError()
        if n == 1:
            return 1
        return n * fact(n - 1)
    if __name__ == '__main__':
       import doctest
       doctest.testmod()





    展开全文
  • python如何调试

    2019-09-23 16:15:54
    使用python自带工具pdb进行调试 法一: 进入调试模式:python-mpdb endorse.py pdb单步调试命令如下: 命令 解释 break或 b设置断点 设置断点 continue或 c ...
  • Python 调试冷知识

    千次阅读 多人点赞 2019-10-23 09:47:32
    对于 python 代码的调试我们通常都是使用 IDE 自带的调试功能。但是 IDE 提供的调试功能存在局限性,例如在测试服务器上调试代码,但是又不可能在测试服务器上安装 IDE 进行调试。这时我们就可以利用下面所讲解的三...
  • Python代码调试

    千次阅读 2018-11-01 14:15:28
    有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的,因此,需要一整套调试程序的手段来修复bug。 第一种方法简单直接粗暴有效,就是用print()把...
  • Python常用的程序调试方法

    千次阅读 2017-11-17 16:01:01
    程序能一次写完并正常运行的概率很小,基本不超过1%,总会有各种各样的bug...下面我们来看下常用的Python调试方法1. 断点打印法第一种方法简单直接粗暴有效,就是用print把可能有问题的变量打印出来看看:err.pydef f
  • python调试

    2019-03-04 17:33:40
    今天在《剑指offer》上看到,面试官特别注重代码的调试能力,如果能够先做单元测试再写代码,那就更叫人另眼相看了。...1.python中的调试 (1)logging方式(和print原理类似) import loggin...
  • Python 调试器入门

    千次阅读 2019-03-11 14:57:41
    本文重点介绍另一种可以节省时间并提高 Python 技能的工具:Python 调试器。 Python 调试Python 标准库提供了一个名为 pdb 的调试器。此调试器提供了调试所需的大多数功能,如断点、单行步进、堆栈帧的检查...
  • 读代码时边调试边看,可以提高速度,顺便偷懒。 想要调试阅读python第三方库,随手一查,找到了大佬使用PDB文章https://blog.csdn.net/ybdesire/article/details/54649211,试了一下,pdb这东西像极了gdb所以上手...
  • 服务器下的python调试方法

    千次阅读 2017-07-07 23:16:37
    安装方式调试方法单步调试python -m ipdb xxx.py 断点在需要打断点的地方加上下述代码from ipdb import set_trace set_trace() 运行到目标位置中断程序,出现提示符,进入ipython环境常用命令 n(下一步) ENTER...
  • 使用Python调试器pdb调试程序(mac)

    千次阅读 2017-08-11 09:54:41
    Launchpad-->终端-->输入python3 -m pdb 然后将Error.py文件拖入终端,相当于输入python3 -m pdb 路径.Error.py 接着开始调试。 或者在代码中开头加上 import pdb,在可能出错的地方加上 pdb.set_trace(),这样就...
  • 使用sublime text搭建Python调试环境

    千次阅读 2018-10-09 20:05:59
    使用Sublime Text搭建python调试环境[转] pycharmt等IDE虽然用着爽,但毕竟在速度、资源上还是比较让人不爽的。 使用IDE无非是图个方便省事,特别是像我这种有些记性差的来说。 IDE说起来方便于的几个地方就是: 1、...
  • linux下python调试方法

    2019-01-24 18:47:01
     http://docs.python.org/library/pdb.html 和 (pdb)help ...python -m pdb myscript.py 常用命令说明:  l #查看运行到哪行代码  n #单步运行,跳过函数  s #单步运行,可进入函数  p ...
  • sublime text3搭建python调试环境

    万次阅读 2017-09-26 21:27:21
    1、安装package control(方便安装插件包); 2、使用package control安装SublimeCode Intel、AutoPEP8、Sublime REPL:  (1)按下CTRL+shift+p挑出命令面板;  (2)输入install package选项并回车,然后分别在...
  • 使用Spyder进行Python调试

    万次阅读 2017-04-23 09:48:43
    相对于PyDev、PyCharm、PTVS这些软件,spyder对内存的需求要小很多,而且也很简单易学。下面做一些简单的介绍。 1、首先是建立工程: 单击 file-----NewProject  在首次建立工程的时候会要求选择工作空间,您...
  • VSCode配置python调试环境

    千次阅读 2019-09-29 22:04:31
    VSCode配置python调试环境 1.下载python解释器 2.在VSCode市场中安装Python插件 4.在用户设置里加两条 5.接下来是正式的调试了 1080 两个数的平方和 Input Output Input示例 Output示例 VSCode配置python调试环境...
  • Windows10 PowerShell Python 调试

    千次阅读 2016-07-29 16:02:05
    Windows10 PowerShell下的Python: Notepad++6.9.2/Windows powershell/Python3.5.2(Print参数发生变化,后更改为Python2.7.12 ------------------Error Logs------------------------------------------------...
  • 之前一直在window下写python脚本,习惯用eclipse调试编辑,突然换到在linux环境下,有点不适应。。。 python有自带的pdb库,可以实现简单的调试功能,基本命令与gdb类似,不过功能不会有gdb那么强大,pdb主要支持多...
1 2 3 4 5 ... 20
收藏数 175,552
精华内容 70,220
关键字:

python调试