python入门基础总结笔记(7)——错误、调试和测试
学习采用的是廖雪峰老师的python教程,很精彩详细,收获很大,聊表感谢!原文链接:https://www.liaoxuefeng.com/wiki/1016959663602400
目录
- 1.错误处理
- 2.调试 (PyCharm)
- 3.单元测试
- 4.文档测试
1.错误处理
在Python中,如果程序运行出现错误,可以事先约定返回一个错误代码,这样,就可以知道是否有错,以及出错的原因。
一旦出错,还要一级一级上报,直到某个函数可以处理该错误(比如,给用户输出一个错误信息)。
高级语言通常都内置了一套try…except…finally…的错误处理机制,python也不例外。
1.1 try…except…finally…
try:
print('try...')
r = 10 / 0 #除法错误
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')
#结果
try... #try中出现错误,不会继续执行后面的程序
except: division by zero #指出错误原因
finally...
END
当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
可以有多个except来捕获不同类型的错误:
try:
print('try...')
r = 10 / int('a')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
finally:
print('finally...')
print('END')
#结果
try...
ValueError: invalid literal for int() with base 10: 'a'
finally...
END
如果没有错误发生,可以在except语句块后面加一个else,当没有错误发生时,会自动执行else语句:
try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else: ###
print('no error!')
finally:
print('finally...')
print('END')
#结果
try...
result: 5.0
no error!
finally...
END
使用try…except捕获错误还有一个巨大的好处,就是可以跨越多层调用:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')
函数main()调用bar(),bar()调用foo(),结果foo()出错了,这时,只要main()捕获到了,就可以处理。
1.2 调用栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。如下:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
bar('0')
main()
运行结果:
$ python3 err.py
Traceback (most recent call last): #告诉我们这是错误的跟踪信息
File "err.py", line 11, in <module>
main() # 调用main()出错了,但原因在第九行
File "err.py", line 9, in main
bar('0') #调用bar('0')出错了,但原因是第6行:
File "err.py", line 6, in bar
return foo(s) * 2 #return foo(s) * 2这个语句出错了,但这还不是最终原因
File "err.py", line 3, in foo
return 10 / int(s) #return 10 / int(s)这个语句出错了,这是错误产生的源头!!!
ZeroDivisionError: division by zero #最终错误源头
1.3 抛出错误
Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。但只有在必要的时候才定义我们自己的错误类型。
首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例:
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')
执行,可以最后跟踪到我们自己定义的错误:
$ python3 err_raise.py
Traceback (most recent call last):
File "err_throw.py", line 11, in <module>
foo('0')
File "err_throw.py", line 8, in foo
raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0
练习
运行下面的代码,根据异常信息进行分析,定位出错误源头,并修复:
from functools import reduce
def str2num(s):
############重点部分
try:
return int(s)
except ValueError:
print('ValueError:input float!')
return float(s)
############
def calc(exp):
ss = exp.split('+')
ns = map(str2num, ss)
return reduce(lambda acc, x: acc + x, ns)
def main():
r = calc('100 + 200 + 345')
print('100 + 200 + 345 =', r)
r = calc('99 + 88 + 7.6')
print('99 + 88 + 7.6 =', r)
main()
100 + 200 + 345 = 645
ValueError:input float
99 + 88 + 7.6 = 194.6
2.调试
突然想起朋友圈的程序员同学一句话:“coding all day,debugging all night”,可以说是完美地诠释了调试以及修复bug的重要性。
在原文中,廖老师提到了好几种python自有的调试方法:
- print(用了这么久才发现真是粗暴呀!)
- 断言(不知为何我用了没有效果?)
- logging(老师评价的最牛B的!但是初学还是觉得麻烦)
- pdb和pdb.set_trace()(无感)
直到最后出现IDE才眼前一亮,要比较爽地设置断点、单步执行,支持调试功能的IDE绝对是必备选项啊!目前比较好的Python IDE有:
Visual Studio Code:https://code.visualstudio.com/,需要安装Python插件。
PyCharm:http://www.jetbrains.com/pycharm/
本人下载的IDE是PyCharm,关于调试方法,找到了一篇详细的指导教程:新手必会,pycharm的调试功能(史上最详篇)
总结文中学习到的调试方法:
- 绿色小瓢虫:所有代码debug模式
- 断点调试:
设置断点(代码前红点)
绿色瓢虫(进入debug)
F7快捷键(进入子函数并且继续单步执行)
F8快捷键(单步调试)
F9快捷键(跳过当前,直接运行到下一断点处)
Shift+F8快捷键:假如进入了一个函数体中,想跳出当前函数体内,返回到调用此函数的地方。
3.单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
比如对函数abs(),我们可以编写出以下几个测试用例:
- 输入正数,比如1、1.2、0.99,期待返回值与输入相同;
- 输入负数,比如-1、-1.2、-0.99,期待返回值与输入相反;
输入0,期待返回0;- 输入非数值类型,比如None、[]、{},期待抛出TypeError。
把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。
练习
对Student类编写单元测试,结果发现测试不通过,请修改Student类,让测试通过:
a.py文件:含待测试模块
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
if self.score >= 60 and self.score<80:
return 'B'
if self.score >= 80 and self.score<=100:
return 'A'
if self.score>=0 and self.score<60:
return 'C'
else:
raise ValueError ('This score is error.')
a_test.py文件:含测试模块
import unittest #编写单元测试,需要引入Python自带的unittest模块
from a import Student #原模块中待测试类导入测试模块
#单元测试模块
class TestStudent(unittest.TestCase):
def test_80_to_100(self):
s1 = Student('Bart', 80)
s2 = Student('Lisa', 100)
self.assertEqual(s1.get_grade(), 'A') #断言函数返回结果是否与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): #输入错误的分数时,我们期待抛出ValueError
s1.get_grade()
with self.assertRaises(ValueError):
s2.get_grade()
if __name__ == '__main__':
unittest.main() # 运行单元测试
- 关于if _name_ == ‘__main__’:
具体可参考博客:如何快速简单粗暴地理解Python中的if name == ‘main’
重点:
当.py文件被直接运行时,if _name_ == '_main__'之下的代码块将被运行;
当.py文件以模块形式被导入时,if _name_ == '_main__'之下的代码块不被运行。
- 也可以将a,b文件代码放在一个文件中运行,略去from a import Student即可。
4.文档测试
如果你经常阅读Python的官方文档,可以看到很多文档都有示例代码。举例:
def abs(n):
'''
Function to get absolute value of number.
Example:
>>> abs(1)
1
>>> abs(-1)
1
>>> abs(0)
0
'''
return n if n >= 0 else (-n)
中间部分为示例代码,目的是更明确地告诉函数的调用者该函数的期望输入和输出。
在Python的交互式环境下输入并执行,结果与文档中的示例代码显示的一致。
Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。
练习
对函数fact(n)编写doctest并执行:
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
else:
return n * fact(n - 1)
if __name__ == '__main__': #运行文档测试!!!
import doctest
doctest.testmod()
当注释发生错误,比如把fact(10)的结果置换成10000,运行结果如下:
File "D:/Pythonhello/b.py", line 7, in __main__.fact
Failed example:
fact(10)
Expected:
10000
Got:
3628800
**********************************************************************
1 items had failures:
1 of 3 in __main__.fact
***Test Failed*** 1 failures.
用“文档测试”(doctest)模块可以检查所写的注释代码是否有错。