Python 代码调试技巧示例代码
Debug对于任何开发人员都是一项非常重要的技能,它能够帮助我们准确的定位错误,发现程序中的bug。python提供了一系列debug的工具和包,可供我们选择。本文将主要阐述如何利用pythondebug相关工具进行debug。
使用pdb进行调试
pdb是python自带的一个包,为python程序提供了一种交互的源代码调试功能,主要特性包括设置断点、单步调试、进入函数调试、查看当前代码、查看栈片段、动态改变变量的值等。pdb提供了一些常用的调试命令,详情见表1。
表1.pdb常用命令
设置断点 |
继续执行程序 |
查看当前行的代码段 |
进入函数 |
执行代码直到从当前函数返回 |
中止并退出 |
执行下一行 |
打印变量的值 |
帮助 |
下面结合具体的实例讲述如何使用pdb进行调试。
清单1.测试代码示例
importpdb a="aaa" pdb.set_trace() b="bbb" c="ccc" final=a+b+c printfinal
开始调试:直接运行脚本,会停留在pdb.set_trace()处,选择n+enter可以执行当前的statement。在第一次按下了n+enter之后可以直接按enter表示重复执行上一条debug命令。
清单2.利用pdb调试
[root@rcc-pok-idg-2255~]#pythonepdb1.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 1importpdb 2a="aaa" 3pdb.set_trace() 4b="bbb" 5c="ccc" 6->final=a+b+c 7printfinal [EOF] (Pdb) [EOF] (Pdb)n >/root/epdb1.py(7)?() ->printfinal (Pdb)
退出debug:使用quit或者q可以退出当前的debug,但是quit会以一种非常粗鲁的方式退出程序,其结果是直接crash。
清单3.退出debug
[root@rcc-pok-idg-2255~]#pythonepdb1.py >/root/epdb1.py(4)?() ->b="bbb" (Pdb)n >/root/epdb1.py(5)?() ->c="ccc" (Pdb)q Traceback(mostrecentcalllast): File"epdb1.py",line5,in? c="ccc" File"epdb1.py",line5,in? c="ccc" File"/usr/lib64/python2.4/bdb.py",line48,intrace_dispatch returnself.dispatch_line(frame) File"/usr/lib64/python2.4/bdb.py",line67,indispatch_line ifself.quitting:raiseBdbQuit bdb.BdbQuit
打印变量的值:如果需要在调试过程中打印变量的值,可以直接使用p加上变量名,但是需要注意的是打印仅仅在当前的statement已经被执行了之后才能看到具体的值,否则会报NameError:
清单4.debug过程中打印变量
[root@rcc-pok-idg-2255~]#pythonepdb1.py >/root/epdb1.py(4)?() ->b="bbb" (Pdb)n >/root/epdb1.py(5)?() ->c="ccc" (Pdb)pb 'bbb' (Pdb) 'bbb' (Pdb)n >/root/epdb1.py(6)?() ->final=a+b+c (Pdb)pc 'ccc' (Pdb)pfinal ***NameError:(Pdb)n >/root/epdb1.py(7)?() ->printfinal (Pdb)pfinal 'aaabbbccc' (Pdb)
使用c可以停止当前的debug使程序继续执行。如果在下面的程序中继续有set_statement()的申明,则又会重新进入到debug的状态,读者可以在代码printfinal之前再加上set_trace()验证。
清单5.停止debug继续执行程序
[root@rcc-pok-idg-2255~]#pythonepdb1.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~]#pythonepdb1.py >/root/epdb1.py(4)?() ->b="bbb" (Pdb)list importpdb a="aaa" pdb.set_trace() ->b="bbb" c="ccc" final=a+b+c pdb.set_trace() printfinal [EOF] (Pdb)c >/root/epdb1.py(8)?() ->printfinal (Pdb)list pdb.set_trace() b="bbb" c="ccc" final=a+b+c pdb.set_trace() ->printfinal [EOF] (Pdb)
在使用函数的情况下进行debug
清单7.使用函数的例子
importpdb defcombine(s1,s2):#definesubroutinecombine,which... s3=s1+s2+s1#sandwichess2betweencopiesofs1,... s3='"'+s3+'"'#enclosesitindoublequotes,... returns3#andreturnsit. a="aaa" pdb.set_trace() b="bbb" c="ccc" final=combine(a,b) printfinal
如果直接使用n进行debug则到final=combine(a,b)这句的时候会将其当做普通的赋值语句处理,进入到printfinal。如果想要对函数进行debug如何处理呢?可以直接使用s进入函数块。函数里面的单步调试与上面的介绍类似。如果不想在函数里单步调试可以在断点处直接按r退出到调用的地方。
清单8.对函数进行debug
[root@rcc-pok-idg-2255~]#pythonepdb2.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() ->defcombine(s1,s2):#definesubroutinecombine,which... (Pdb)n >/root/epdb2.py(4)combine() ->s3=s1+s2+s1#sandwichess2betweencopiesofs1,... (Pdb)list importpdb defcombine(s1,s2):#definesubroutinecombine,which... ->s3=s1+s2+s1#sandwichess2betweencopiesofs1,... s3='"'+s3+'"'#enclosesitindoublequotes,... returns3#andreturnsit. a="aaa" pdb.set_trace() b="bbb" c="ccc" (Pdb)n >/root/epdb2.py(5)combine() ->s3='"'+s3+'"'#enclosesitindoublequotes,... (Pdb)n >/root/epdb2.py(6)combine() ->returns3#andreturnsit. (Pdb)n --Return-- >/root/epdb2.py(6)combine()->'"aaabbbaaa"' ->returns3#andreturnsit. (Pdb)n >/root/epdb2.py(13)?() ->printfinal (Pdb)
在调试的时候动态改变值。在调试的时候可以动态改变变量的值,具体如下实例。需要注意的是下面有个错误,原因是b已经被赋值了,如果想重新改变b的赋值,则应该使用!B。
清单9.在调试的时候动态改变值
[root@rcc-pok-idg-2255~]#pythonepdb2.py >/root/epdb2.py(10)?() ->b="bbb" (Pdb)var="1234" (Pdb)b="avfe" ***Thespecifiedobject'="avfe"'isnotafunction orwasnotfoundalongsys.path. (Pdb)!b="afdfd" (Pdb)
pdb调试有个明显的缺陷就是对于多线程,远程调试等支持得不够好,同时没有较为直观的界面显示,不太适合大型的python项目。而在较大的python项目中,这些调试需求比较常见,因此需要使用更为高级的调试工具。接下来将介绍PyCharmIDE的调试方法.
使用PyCharm进行调试
PyCharm是由JetBrains打造的一款PythonIDE,具有语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制等功能,同时提供了对Django开发以及GoogleAppEngine的支持。分为个人独立版和商业版,需要license支持,也可以获取免费的30天试用。试用版本的Pycharm可以在官网上下载,下载地址为:http://www.jetbrains.com/pycharm/download/index.html。PyCharm同时提供了较为完善的调试功能,支持多线程,远程调试等,可以支持断点设置,单步模式,表达式求值,变量查看等一系列功能。PyCharmIDE的调试窗口布局如图1所示。
图1.PyCharmIDE窗口布局
下面结合实例讲述如何利用PyCharm进行多线程调试。具体调试所用的代码实例见清单10。
清单10.PyCharm调试代码实例
__author__='zhangying' #!/usr/bin/python importthread importtime #Defineafunctionforthethread defprint_time(threadName,delay): count=0 whilecount<5: count+=1 print"%s:%s"%(threadName,time.ctime(time.time())) defcheck_sum(threadName,valueA,valueB): print"tocalculatethesumoftwonumberher" result=sum(valueA,valueB) print"theresultis",result; defsum(valueA,valueB): ifvalueA>0andvalueB>0: returnvalueA+valueB defreadFile(threadName,filename): file=open(filename) forlineinfile.xreadlines(): printline 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:unabletostartthread" while1: # print"end" pass
在调试之前通常需要设置断点,断点可以设置在循环或者条件判断的表达式处或者程序的关键点。设置断点的方法非常简单:在代码编辑框中将光标移动到需要设置断点的行,然后直接按Ctrl+F8或者选择菜单"Run"->"ToggleLineBreakPoint",更为直接的方法是双击代码编辑处左侧边缘,可以看到出现红色的小圆点(如图2)。当调试开始的时候,当前正在执行的代码会直接显示为蓝色。下图中设置了三个断点,蓝色高亮显示的为正在执行的代码。
图2.断点设置
表达式求值:在调试过程中有的时候需要追踪一些表达式的值来发现程序中的问题,Pycharm支持表达式求值,可以通过选中该表达式,然后选择“Run”->”EvaluateExpression”,在出现的窗口中直接选择Evaluate便可以查看。
Pychar同时提供了Variables和Watches窗口,其中调试步骤中所涉及的具体变量的值可以直接在variable一栏中查看。
图3.变量查看
如果要动态的监测某个变量可以直接选中该变量并选择菜单”Run”->”AddWatch”添加到watches栏中。当调试进行到该变量所在的语句时,在该窗口中可以直接看到该变量的具体值。
图4.监测变量
对于多线程程序来说,通常会有多个线程,当需要debug的断点分别设置在不同线程对应的线程体中的时候,通常需要IDE有良好的多线程调试功能的支持。Pycharm中在主线程启动子线程的时候会自动产生一个Dummy开头的名字的虚拟线程,每一个frame对应各自的调试帧。如图5,本实例中一共有四个线程,其中主线程生成了三个线程,分别为Dummy-4,Dummy-5,Dummy-6.其中Dummy-4对应线程1,其余分别对应线程2和线程3。
图5.多线程窗口
当调试进入到各个线程的子程序时,Frame会自动切换到其所对应的frame,相应的变量栏中也会显示与该过程对应的相关变量,如图6,直接控制调试按钮,如setpin,stepover便可以方便的进行调试。
图6.子线程调试
使用PyDev进行调试
PyDev是一个开源的的plugin,它可以方便的和Eclipse集成,提供方便强大的调试功能。同时作为一个优秀的PythonIDE还提供语法错误提示、源代码编辑助手、QuickOutline、GlobalsBrowser、HierarchyView、运行等强大功能。下面讲述如何将PyDev和Eclipse集成。在安装PyDev之前,需要先安装Java1.4或更高版本、Eclipse以及Python。第一步:启动Eclipse,在Eclipse菜单栏中找到Help栏,选择Help>InstallNewSoftware,并选择Addbutton,添加Ptdev的下载站点http://pydev.org/updates。选择PyDev之后完成余下的步骤便可以安装PyDev。
图7.安装PyDev
安装完成之后需要配置Python解释器,在Eclipse菜单栏中,选择Window>Preferences>Pydev>Interpreter–Python。Python安装在C:\Python27路径下。单击New,选择Python解释器python.exe,打开后显示出一个包含很多复选框的窗口,选择需要加入系统PYTHONPATH的路径,单击OK。
图8.配置PyDev
在配置完Pydev之后,可以通过在Eclipse菜单栏中,选择File>New>Project>Pydev>PydevProject,单击Next创建Python项目,下面的内容假设python项目已经创建,并且有个需要调试的脚本remote.py(具体内容如下),它是一个登陆到远程机器上去执行一些命令的脚本,在运行的时候需要传入一些参数,下面将详细讲述如何在调试过程中传入参数.
清单11.Pydev调试示例代码
#!/usr/bin/envpython importos deftelnetdo(HOST=None,USER=None,PASS=None,COMMAND=None):#defineafunction importtelnetlib,sys ifnotHOST: try: HOST=sys.argv[1] USER=sys.argv[2] PASS=sys.argv[3] COMMAND=sys.argv[4] except: print"Usage:remote.pyhostuserpasscommand" return tn=telnetlib.Telnet()# try: tn.open(HOST) except: print"Cannotopenhost" return tn.read_until("login:") tn.write(USER+'\n') ifPASS: tn.read_until("Password:") tn.write(PASS+'\n') tn.write(COMMAND+'\n') tn.write("exit\n") tmp=tn.read_all() tn.close() deltn returntmp if__name__=='__main__': printtelnetdo()
在调试的时候有些情况需要传入一些参数,在调试之前需要进行相应的配置以便接收所需要的参数,选择需要调试的程序(本例remote.py),该脚本在debug的过程中需要输入四个参数:host,user,password以及命令。在eclipse的工程目录下选择需要debug的程序,单击右键,选择“DebugAs”->“DebugConfigurations”,在ArgumentsTab页中选择“Variables”。如下图9所示.
图9.配置变量
在窗口”SelectVariable”之后选择“EditVaruables”,出现如下窗口,在下图中选择”New”并在弹出的窗口中输入对应的变量名和值。特别需要注意的是在值的后面一定要有空格,不然所有的参数都会被当做第一个参数读入。
图10.添加具体变量
按照以上方式依次配置完所有参数,然后在”selectvariable“窗口中安装参数所需要的顺序依次选择对应的变量。配置完成之后状态如下图11所示。
图11.完成配置
选择Debug便可以开始程序的调试,调试方法与eclipse内置的调试功能的使用相似,并且支持多线程的debug,这方面的文章已经有很多,读者可以自行搜索阅读,或者参考”使用Eclipse平台进行调试“一文。
使用日志功能达到调试的目的
日志信息是软件开发过程中进行调试的一种非常有用的方式,特别是在大型软件开发过程需要很多相关人员进行协作的情况下。开发人员通过在代码中加入一些特定的能够记录软件运行过程中的各种事件信息能够有利于甄别代码中存在的问题。这些信息可能包括时间,描述信息以及错误或者异常发生时候的特定上下文信息。最原始的debug方法是通过在代码中嵌入print语句,通过输出一些相关的信息来定位程序的问题。但这种方法有一定的缺陷,正常的程序输出和debug信息混合在一起,给分析带来一定困难,当程序调试结束不再需要debug输出的时候,通常没有很简单的方法将print的信息屏蔽掉或者定位到文件。python中自带的logging模块可以比较方便的解决这些问题,它提供日志功能,将logger的level分为五个级别,可以通过Logger.setLevel(lvl)来设置。默认的级别为warning。
表2.日志的级别
详细的信息,在追踪问题的时候使用 |
正常的信息 |
一些不可预见的问题发生,或者将要发生,如磁盘空间低等,但不影响程序的运行 |
由于某些严重的问题,程序中的一些功能受到影响 |
严重的错误,或者程序本身不能够继续运行 |
logginglib包含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的实例,则使用默认的rootlogger,通过logging.getLogger()或者logging.getLogger("")得到rootlogger实例。
- Handler:Handler用来处理信息的输出,可以将信息输出到控制台,文件或者网络。可以通过Logger.addHandler()来给logger对象添加handler,常用的handler有StreamHandler和FileHandler类。StreamHandler发送错误信息到流,而FileHandler类用于向文件输出日志信息,这两个handler定义在logging的核心模块中。其他的hander定义在logging.handles模块中,如HTTPHandler,SocketHandler。
- Formatter:Formatter则决定了log信息的格式,格式使用类似于%(
)s的形式来定义,如'%(asctime)s-%(levelname)s-%(message)s',支持的key可以在python自带的文档LogRecordattributes中查看。 - Filter:Filter用来决定哪些信息需要输出。可以被handler和logger使用,支持层次关系,比如如果设置了filter为名称为A.B的logger,则该logger和其子logger的信息会被输出,如A.B,A.B.C.
清单12.日志使用示例
importlogging 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('itisadebuginfoforlog1') LOG1.info('normalinforforlog1') LOG1.warning('warninginfoforlog1:b.c') LOG1.error('errorinfoforlog1:abcd') LOG1.critical('criticalinfoforlog1:notworked') LOG2.debug('debuginfoforlog2') LOG2.info('normalinfoforlog2') LOG2.warning('warninginfoforlog2') LOG2.error('error:b.c') LOG2.critical('critical')
上例设置了filterb,则b.c为b的子logger,因此满足过滤条件该logger相关的日志信息会被输出,而其他不满足条件的logger(这里是d.e)会被过滤掉。
清单13.输出结果
b.c2011-11-2511:07:29,733INFOnormalinforforlog1
b.c2011-11-2511:07:29,733WARNINGwarninginfoforlog1:b.c
b.c2011-11-2511:07:29,733ERRORerrorinfoforlog1:abcd
b.c2011-11-2511:07:29,733CRITICALcriticalinfoforlog1:notworked
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= codeexample: #!/usr/bin/python importthread importtime importlogging importlogging.config logging.config.fileConfig('logging.conf') #createlogger logger=logging.getLogger('simpleExample') #Defineafunctionforthethread defprint_time(threadName,delay): logger.debug('thread1callprint_timefunctionbody') count=0 logger.debug('count:%s',count)
总结
全文介绍了python中debug的几种不同的方式,包括pdb模块、利用PyDev和Eclipse集成进行调试、PyCharm以及Debug日志进行调试,希望能给相关python使用者一点参考。更多关于pythondebugger的资料可以参见参考资料。
到此这篇关于Python代码调试技巧示例代码的文章就介绍到这了,更多相关Python代码调试技巧内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。