python调试_python调试技巧 - CSDN
精华内容
参与话题
  • python如何调试

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

    使用python自带工具pdb进行调试

    法一:

    进入调试模式:python -m pdb endorse.py

    pdb单步调试命令如下:

    命令

    解释

    break 或 b 设置断点

    设置断点

    continue 或 c

    继续执行程序

    list 或 l

    查看当前行的代码段

    step 或 s

    进入函数

    return 或 r

    执行代码直到从当前函数返回

    exit 或 q

    中止并退出

    next 或 n

    执行下一行

    pp

    打印变量的值

    a

    查看全部栈内变量

    介绍下常用命令使用方法:

    先上代码 endorse.py

    import os,re
    from openpyxl import load_workbook
    path=r"C:\huyunan\test\excel\hu\IMS40701DM0hu.xlsx"
    file = open(r'center.txt','wb')
    
    workbook  = load_workbook(path)
    worksheets = workbook.sheetnames
    for sheet in worksheets:
        worksheet1 = workbook[u''+sheet]
        num_rows = worksheet1.max_row
        num_cols = worksheet1.max_column
        for rown in range(num_rows):
            for coln in range(num_cols):
                cell = worksheet1.cell(rown+1,coln+1).comment
                if cell is not None and "center" in cell.text:
                    file.write(cell.text.encode())
                    print(cell.text)
    file.close()

    进入调试模式输入b 11(11为行号)

    之后输入c指令执行代码,会在11行中断

    n命令是单步执行,不会进入函数内部。

    s命令是单步执行,会进入函数内部。

    指令a是打印出当前函数的参数值

    指令j 是跳转到某一行执行有点类似b和c指令结合,具体使用 j xx(xx为行数)

    指令q,退出pdb调试模式

    现在查看一下num_rows变量的值:pp num_rows

    法二:

    在py文件中导入pdb,然后再想定断点处加上:pdb.set_trace()

    执行:python endorse.py

    走到断点处会自动进入pdb。

    展开全文
  • 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-10-23 09:47:32
    对于 python 代码的调试我们通常都是使用 IDE 自带的调试功能。但是 IDE 提供的调试功能存在局限性,例如在测试服务器上调试代码,但是又不可能在测试服务器上安装 IDE 进行调试。这时我们就可以利用下面所讲解的三...

     

    作者 | 喵叔

    责编 | 刘静

    出品 | CSDN(ID:CSDNnews)

    对于 python 代码的调试我们通常都是使用 IDE 自带的调试功能。但是 IDE 提供的调试功能存在局限性,例如在测试服务器上调试代码,但是又不可能在测试服务器上安装 IDE 进行调试。这时我们就可以利用下面所讲解的三个工具进行调试。

    零、准备调试代码

    在讲解三个调试工具前,我们先编写待调试的代码。代码很简单,就是计算两个数的商。我们在编写代码的时候故意留下了除数为 0 的 bug。

    def division(start, end):
        for i in range(start, end, -1):
            num1 = i
            num2 = i - 1
            result = num1 / num2
            print(result)
    
    if __name__ == '__main__':
        division(10, 0)

     

    PySnooper

     

    PySnooper 是 Python 的第三方工具库,它可以精确的显示代码的执行时间、执行顺序和代码中的局部变量值的变化等。PySnooper 使用方法很简单,只需要将它作为装饰器来使用即可。下面我们来看一下具体使用步骤:

    1. 安装 PySnooper
    在控制台输入如下命令:

    pip install pysnooper

    2. 加入 PySnooper

    • 首先需要引入 PySnooper

    import pysnooper
    • 接着在需要测试的函数上加上 pysnooper 装饰器

    @pysnooper.snoop()
    def division(start, end):
        for i in range(start, end, -1):
            num1 = i
            num2 = i - 1
            result = num1 / num2
            print(result)
    
    
    if __name__ == '__main__':
        division(10, 0)

    在控制台输入命令:

    python text.py

    运行代码后,控制台输出如下内容

    上图只截取了 PySnooper 输出日志的开头内容和最后结尾的内容。从截图中我们可以看到 PySnooper 输出了每行代码的运行顺序、运行时间和代码运行中变量值的变化,以及报错信息。在实际项目中 PySnooper 输出的日志内容会很多,在控制台查看会很不方便,这时我们可以将日志输出到本地文件中,我们只需在 PySnooper 装饰器中加入日志保存路径即可:

    @pysnooper.snoop('/app/project_log.log')
    @pysnooper.snoop(prefix='MyCompanyName: ')
    

    前面我们所讲的都是在函数上利用装饰器来监控整个函数,但是在实际项目中往往一个函数内容会很多,如果监控整个函数会导致输出的日志过多,这时我们就可以利用 PySnooper 的局部监控功能来监控函数中需要监控的代码片段。现在我们来修改一下代码,只监控输出的值:

    import pysnooper
    
    def division(start, end):
        for i in range(start, end, -1):
            with pysnooper.snoop():
                num1 = i
                num2 = i - 1
                result = num1 / num2
            print(result)
    
    
    if __name__ == '__main__':
        division(10, 0)

     

    Better-exceptions

     

    Better-exceptions 同样是 Python 的第三方工具库,它出现的原因是其实很简单就是“美化异常信息”(是不是感觉作者很任性)。Better-exceptions 主要使用了 Python 的 sys 模块的 excepthook 方法,这个方法在当系统抛出异常时,解释器就会调用它,同时传递三个参数:异常类、异常实例和 traceback 对象,这就说明我们可以重写这个方法来捕获系统异常。但是,因为我们可以重写 excepthook 方法来捕获系统异常,因此 Better-exceptions 对与 Web 框架来说是不起任何作用的,因为 Web 框架都已经处理了系统抛出的异常,不会再以 hook 的方式触发 Better-exceptions 。下面我们就来看一下该怎么用。

    1. 安装 Better-exceptions

    • 首先在控制台输入如下命令:

    pip install better-exceptions
    • 接着我们在控制台输入如下代码,来设置环境变量:

    setx BETTER_EXCEPTIONS 1

        2. 调试代码
            在控制台输入命令:

    python text.py

     

    代码运行后,控制台输出如下图:

    从上面的图我们可以看到,Better-exceptions 对异常代码进行了着色,并对产生异常的变量值进行了输出。通过这两项内容我们就可以很快捷的看到具体报错位置和报错原因。这里有需要注意的地方就是,在 Windows 系统下输出的日志会存在乱码问题,这是因为 Better-exceptions 的编码格式造成的。要解决这个问题我们只需要修改 better-exceptions 目录下的 encoding.py 文件,讲文件中的 ENCODING = locale.getpreferredencoding() 修改为 ENCODING = 'utf-8'即可。

     

    PDB

     

    PDB 是 Python 内置的模块,我们可以利用 PDB 设置断点和跟踪调试。PDB 的使用不需要再安装第三方插件,只需要在命令行输入如下命令:

    python -m pdb Test.py

     

     

    命令执行后将会进入 PDB 调试模式。如果需要在代码中加入断点,只需要在需要加入断点的位置

     

    总结

     

    我们讲解了 PySnooper 、Better-exceptions 和 PDB 的用法,这三种方法一般都使用在服务器上,这里我推荐使用Better-exceptions,因为它对代码的侵入性很小,几乎不需要改变代码。

    作者简介:朱钢,笔名喵叔,CSDN博客专家,.NET高级开发工程师,7年一线开发经验,参与过电子政务系统和AI客服系统的开发,以及互联网招聘网站的架构设计,目前就职于北京恒创融慧科技发展有限公司,从事企业级安全监控系统的开发。

    声明:本文系作者独立观点,不代表CSDN立场。

    展开全文
  • 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程序

    千次阅读 2018-03-27 20:39:57
    转载地址:https://blog.csdn.net/theonegis/article/details/52211799Python提供类似于C++ gdb的调试工具pdb,我们可以在Linux下使用pdb在命令行下进行Python程序的调试。 官方参考网站: Python2: ...
  • Python代码调试

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

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

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

    千次阅读 2017-01-13 11:15:37
    我们知道,调试对程序员来说是非常重要的,利用好调试能够大大提高我们发现和修改程序的Bug,在python调试的方法主要有两种。 第一种是从命令行运行: 可以在命令行中使用Python debugger 运行一个脚本,如: $...
  • vscode无法调试python问题

    万次阅读 2018-12-11 17:24:43
    vscode无法调试python问题 提示调试适配器出错 1、保证已安装python调试插件,并升级到最新版本。 2、查看Python版本,升级到最新版本。 3、查看python包管理工具pip版本,升级到最新版本。 再尝试调试 ...
  • Visual Studio Code 简单调试Python代码

    万次阅读 2018-09-24 22:52:56
    目录VS Code 简单调试Python代码一、 首先确保 Python已经成功安装二、 安装Python插件,然后重新载入:三、选择一个文件夹打开四、 选择Debug--&gt; 添加配置五、修改launch.json 配置文件(可选)六、 创建...
  • 使用Sublime Text搭建python调试环境

    万次阅读 2014-08-14 16:39:06
    pycharm虽然用着爽,但经常
  • Linux下使用命令行调试Python程序

    万次阅读 2016-08-15 15:44:26
    Python提供类似于C++ gdb的调试工具pdb,我们可以在Linux下使用pdb在命令行下进行Python程序的调试。 官方参考网站: Python2: https://docs.python.org/2/library/pdb.html Python3: ...
  • 今天才发现原来IDLE也自带了调试器。 关于如何在 IDLE 中进行调试网上的教程讲的很模糊,于是自己整理了一下,和大家分享~ 第一步:打开python shell,并在shell中打开debuger   第二步:从该shell中打开想要...
  • vscode 调试python代码时添加参数(args)

    万次阅读 多人点赞 2018-10-17 10:15:01
    前提:代码中设置了arg paser,需要手动设置,VS code的debug没有简介的添加参数的方式。解决方式如下: 打开Debug-&gt;Open Configurations 在对应的代码块中添加args,如下图(注意参数之间需要用字符串分割...
  • Python如何用自带的IDLE进行调试DEBUG

    万次阅读 2017-05-30 10:28:51
    1.在编辑的py文件中,在想要调试的语句上,加上断点:右键:set Breakpoint,clear Breakpoint可以清除断点。 2.运行该py文件,快捷键:fn+f5 在出现的shell窗口中,点击菜单栏Debug中的Debugger,出现调试控制...
  • VSCode编译、调试Python(Python in VSCode)

    万次阅读 2018-10-02 10:41:07
    VSCode是微软开发的轻量级编辑器,加装扩展程序后,可以用来编译、调试程序,十分方便. VScode 个人觉得VSCode的优点主要有: 轻量级,相较于VS Studio,VSCode要小地多 跨平台,VSCode在Windows, MacOS和Linux上均...
  • vsCode调试python无法进入断点可能原因 1.已安装python 2.vsCode已安装python插件 3.情况如下 点击“调试”后,出来调试工具栏,但一直无法进入断点修改代码后调试,问题解决了,成功进入断点,原因是安装的python3...
  • 1.首先在–所有程序–打开python IDLE如图:2.打开你的py脚本文件 file–open3.在如上脚本窗口点击–Run–Run module(F5)打开python shell窗口4.在如上python shell窗口点击Debug– Debugger打开Debug Control窗口...
  • VSCode搭建Python环境及远程调试方法

    万次阅读 2018-10-11 16:58:31
    下载安装VSCode ...   安装中文扩展,设置中文 ...安装完成后,VSCode默认显示为英文,需要安装插件才能显示中文。...菜单查看-扩展或者Ctrl + Shift + X,在商店中搜索“Chinese”,找到“适用于 VS Code 的中文...
1 2 3 4 5 ... 20
收藏数 171,972
精华内容 68,788
关键字:

python调试