调试
每个程序能一次写完并正常运行的概率基本不超过1%,因为总会有各种各样的bug需要修正。有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的,因此,需要一整套调试程序的手段来修复bug。
第一种方法简单直接粗暴有效,就是用print()把可能有问题的变量打印出来看看:
def foo(s):
n = int(s)
print('>>> n = %d' % n)
return 10 / n
def main():
foo('0')
main()
输出结果:
>>> n = 0
Traceback (most recent call last):
File "F:/python文件/tiaoshi/__init__.py", line 9, in <module>
main()
File "F:/python文件/tiaoshi/__init__.py", line 7, in main
foo('0')
File "F:/python文件/tiaoshi/__init__.py", line 4, in foo
return 10 / n
ZeroDivisionError: division by zero
当然这种方法存在着弊端,运行结果中包含很多没有意义的信息,所以,出现了第二种方法:
断言
凡是用print()来辅助查看的地方,都可以用断言(assert)来替代:
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
main()
assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。
如果断言失败,assert语句本身就会抛出AssertionError:
assert n != 0, 'n is zero!'
AssertionError: n is zero!
logging
把print()替换为logging是第3种方式,和assert比,logging不会抛出错误,而且可以输出到文件:
>>> import logging
>>> s = '0'
>>> n = int(s)
>>> logging.info("n=%d"%n)
>>> print(10/n)
但我们发现输出结果却很简单草率:
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
print(10/n)
ZeroDivisionError: division by zero
然后我们加一句配置再次尝试:
logging.basicConfig(level=logging.INFO)
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的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。