调试Python程序代码的几种方法总结


Posted in Python onApril 28, 2015

程序能一次写完并正常运行的概率很小,基本不超过1%。总会有各种各样的bug需要修正。有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的,因此,需要一整套调试程序的手段来修复bug。

第一种方法简单直接粗暴有效,就是用print把可能有问题的变量打印出来看看:

# err.py
def foo(s):
  n = int(s)
  print '>>> n = %d' % n
  return 10 / n

def main():
  foo('0')

main()

执行后在输出中查找打印的变量值:

$ python err.py
>>> n = 0
Traceback (most recent call last):
 ...
ZeroDivisionError: integer division or modulo by zero

用print最大的坏处是将来还得删掉它,想想程序里到处都是print,运行结果也会包含很多垃圾信息。所以,我们又有第二种方法。
断言

凡是用print来辅助查看的地方,都可以用断言(assert)来替代:

# err.py
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:

$ python err.py
Traceback (most recent call last):
 ...
AssertionError: n is zero!

程序中如果到处充斥着assert,和print相比也好不到哪去。不过,启动Python解释器时可以用-O参数来关闭assert:

$ python -O err.py
Traceback (most recent call last):
 ...
ZeroDivisionError: integer division or modulo by zero

关闭后,你可以把所有的assert语句当成pass来看。
logging

把print替换为logging是第3种方式,和assert比,logging不会抛出错误,而且可以输出到文件:

# err.py
import logging

s = '0'
n = int(s)
logging.info('n = %d' % n)
print 10 / n

logging.info()就可以输出一段文本。运行,发现除了ZeroDivisionError,没有任何信息。怎么回事?

别急,在import logging之后添加一行配置再试试:

import logging
logging.basicConfig(level=logging.INFO)

看到输出了:

$ python err.py
INFO:root:n = 0
Traceback (most recent call last):
 File "err.py", line 8, in <module>
  print 10 / n
ZeroDivisionError: integer division or modulo by zero

这就是logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。

logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
pdb

第4种方式是启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。我们先准备好程序:

# err.py
s = '0'
n = int(s)
print 10 / n

然后启动:

$ python -m pdb err.py
> /Users/michael/Github/sicp/err.py(2)<module>()
-> s = '0'

以参数-m pdb启动后,pdb定位到下一步要执行的代码-> s = '0'。输入命令l来查看代码:

(Pdb) l
 1   # err.py
 2 -> s = '0'
 3   n = int(s)
 4   print 10 / n
[EOF]

输入命令n可以单步执行代码:

(Pdb) n
> /Users/michael/Github/sicp/err.py(3)<module>()
-> n = int(s)
(Pdb) n
> /Users/michael/Github/sicp/err.py(4)<module>()
-> print 10 / n

任何时候都可以输入命令p 变量名来查看变量:

(Pdb) p s
'0'
(Pdb) p n
0

输入命令q结束调试,退出程序:

(Pdb) n
ZeroDivisionError: 'integer division or modulo by zero'
> /Users/michael/Github/sicp/err.py(4)<module>()
-> print 10 / n
(Pdb) q

这种通过pdb在命令行调试的方法理论上是万能的,但实在是太麻烦了,如果有一千行代码,要运行到第999行得敲多少命令啊。还好,我们还有另一种调试方法。
pdb.set_trace()

这个方法也是用pdb,但是不需要单步执行,我们只需要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继续运行:

$ python err.py 
> /Users/michael/Github/sicp/err.py(7)<module>()
-> print 10 / n
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
 File "err.py", line 7, in <module>
  print 10 / n
ZeroDivisionError: integer division or modulo by zero

这个方式比直接启动pdb单步调试效率要高很多,但也高不到哪去。
IDE

如果要比较爽地设置断点、单步执行,就需要一个支持调试功能的IDE。目前比较好的Python IDE有PyCharm:

http://www.jetbrains.com/pycharm/

另外,Eclipse加上pydev插件也可以调试Python程序。
小结

写程序最痛苦的事情莫过于调试,程序往往会以你意想不到的流程来运行,你期待执行的语句其实根本没有执行,这时候,就需要调试了。

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

Python 相关文章推荐
Python实现端口复用实例代码
Jul 03 Python
pymssql数据库操作MSSQL2005实例分析
May 25 Python
Python程序员面试题 你必须提前准备!(答案及解析)
Jan 23 Python
Django框架实现逆向解析url的方法
Jul 04 Python
对Python中创建进程的两种方式以及进程池详解
Jan 14 Python
Appium Python自动化测试之环境搭建的步骤
Jan 23 Python
用Python逐行分析文件方法
Jan 28 Python
Python如何使用Gitlab API实现批量的合并分支
Nov 27 Python
关于tf.reverse_sequence()简述
Jan 20 Python
Python GUI库PyQt5图形和特效样式QSS介绍
Feb 25 Python
sqlalchemy实现时间列自动更新教程
Sep 02 Python
python爬取youtube视频的示例代码
Mar 03 Python
解析Python中的异常处理
Apr 28 #Python
python调用java模块SmartXLS和jpype修改excel文件的方法
Apr 28 #Python
Python EOL while scanning string literal问题解决方法
Sep 18 #Python
python中尾递归用法实例详解
Apr 28 #Python
在Python中使用元类的教程
Apr 28 #Python
python删除列表中重复记录的方法
Apr 28 #Python
python3实现短网址和数字相互转换的方法
Apr 28 #Python
You might like
PHP正则替换函数preg_replace和preg_replace_callback使用总结
2014/09/22 PHP
PHP实现的注册,登录及查询用户资料功能API接口示例
2017/06/06 PHP
PHP实现绘制二叉树图形显示功能详解【包括二叉搜索树、平衡树及红黑树】
2017/11/16 PHP
PHP单例模式数据库连接类与页面静态化实现方法
2019/03/20 PHP
Web开发之JavaScript
2012/03/29 Javascript
浅析document.createDocumentFragment()与js效率
2013/07/08 Javascript
教你使用javascript简单写一个页面模板引擎
2015/05/05 Javascript
JavaScript数据结构与算法之栈与队列
2016/01/29 Javascript
js判断手机系统是android还是ios
2017/03/07 Javascript
微信小程序教程系列之新建页面(4)
2017/04/17 Javascript
Javasript设计模式之链式调用详解
2018/04/26 Javascript
Flutter 超实用简单菜单弹出框 PopupMenuButton功能
2019/08/06 Javascript
vue实现计步器功能
2019/11/01 Javascript
jQuery 函数实例分析【函数声明、函数表达式、匿名函数等】
2020/05/19 jQuery
微信小程序实现音乐播放页面布局
2020/12/11 Javascript
微信小程序学习之自定义滚动弹窗
2020/12/20 Javascript
[00:52]玛尔斯技能全介绍
2019/03/06 DOTA
python操作xml文件示例
2014/04/07 Python
Python cookbook(数据结构与算法)将多个映射合并为单个映射的方法
2018/04/19 Python
spark dataframe 将一列展开,把该列所有值都变成新列的方法
2019/01/29 Python
利用python实现周期财务统计可视化
2019/08/25 Python
Python object类中的特殊方法代码讲解
2020/03/06 Python
大数据分析用java还是Python
2020/07/06 Python
HTML5 video 视频标签使用介绍
2014/02/03 HTML / CSS
AmazeUI底部导航栏与分享按钮的示例代码
2020/08/18 HTML / CSS
英国厨房与餐具用品为主的设计品牌:Joseph Joseph
2018/04/26 全球购物
第一范式(1NF)、第二范式(2NF)和第三范式(3NF)之间的区别是什么?
2016/04/28 面试题
大学自主招生自荐信
2013/12/16 职场文书
战友聚会邀请函
2014/01/18 职场文书
中学自我评价
2014/01/31 职场文书
优秀共产党员演讲稿
2014/09/04 职场文书
2015年公共机构节能宣传周活动总结
2015/03/26 职场文书
2015年幼儿园学期工作总结
2015/05/22 职场文书
python 爬取华为应用市场评论
2021/05/29 Python
mysql 索引的数据结构为什么要采用B+树
2022/04/26 MySQL
css让页脚保持在底部位置的四种方案
2022/07/23 HTML / CSS