目录
错误、调试和测试
错误处理
try...except...finally...
- 在程序运行过程中,可能出现各种异常,比如整数除以零,值类型错误,从网络中抓取数据时网络突然中断等等,都可能导致程序无法再运行下去而中止,为此有必要提供一种机制能够及时捕获该异常进行处理使得程序能够继续运行下去。python中提供了try...except...finally...机制来进行处理,先看下面例子:
try: #测试代码段 print('程序开始运行...') n = input('请输入一个数字:') res = 1024 / int(n) print('结果是:', res) except ValueError as e: #捕捉ValueError类型的错误 print('错误是:', e) except ZeroDivisionError as e: #捕捉ZeroDivisionError类型的错误 print('错误是:', e) else: #程序未出错时运行的代码段 print('程序未出错!') finally: #程序最后运行的代码段 print('程序运行结束!')
程序运行的结果如下:
程序开始运行... 请输入一个数字:k 错误是: invalid literal for int() with base 10: 'k' 程序运行结束!
程序开始运行... 请输入一个数字:0 错误是: division by zero 程序运行结束!
程序开始运行... 请输入一个数字:512 结果是: 2.0 程序未出错! 程序运行结束!
该机制的结构是:代码测试代码块(try)——捕捉错误代码块(except)——最后运行的代码块(finally)。把有可能出错的代码段放入try代码块中,在该代码段中如果执行到某一句出现了异常,那么try代码段中剩余的语句就不会接着执行了(从上面前两个执行结果可以看出),解释器捕捉到该错误后转到相应的异常处理代码块(except)。若没有出现异常,那么就会执行else代码块。最后执行finally代码块(如果有finally代码块就一行会执行)。
-
这里要强调,错误也属于一种类,所有的错误均继承自
BaseException
类。在捕捉某一种错误时,其子类也一并“一网打尽”。比如:try: fun() except ValueError as e: print('错误是:', e) except UnicodeError as e: print('错误是:', e)
这里是ValueError的子类,故UnicodeError的错误也会由第一个except一并捕捉,从而第二个except捕捉不到错误。常见的错误类型及继承关系点这里查看。使用
try...except
捕获错误还有一个巨大的好处,就是可以跨越多层捕捉,比如,main()调用f1(),f1()调用f2(),在函数f2()中出现了错误,但我们只需在main()函数中捕捉到了就可以进行处理。
异常栈
- 如果程序中出现了错误,但我们没对对它进行捕捉,那么该错误就会一直向上抛,最终由解释器进行捕获并处理,但同时程序执行到这里也就结束了,所以最好能有我们自己捕获,这样捕捉到该错误后我们能够对它进行处理,同时程序还能够继续执行下去。下面来看如何利用异常栈来定位程序出错位置。
1 # -*- coding: utf-8 -*- 2 3 def f1(n): 4 print(int(n) / 1024, 'kb') 5 6 def f2(n): 7 f1(n) 8 9 def main(n): 10 f2(n) 11 12 main('l')
运行如下:
File "C:/Users/Whisky/.spyder-py3/temp.py", line 12, in <module> main('l') File "C:/Users/Whisky/.spyder-py3/temp.py", line 10, in main f2(n) File "C:/Users/Whisky/.spyder-py3/temp.py", line 7, in f2 f1(n) File "C:/Users/Whisky/.spyder-py3/temp.py", line 4, in f1 print(int(n) / 1024, 'kb') ValueError: invalid literal for int() with base 10: 'l'
通过以上异常栈信息,就能够准确定位出出错的位置,首先在该模块中第12行main('l')这一句出错了,进一步往下看,在第10行main()中f2(n)这一句出错了,然而这不是最终原因,继续往下看是在第7行,f2函数中的f1(n)这一句错了,接着,最终原因是第4行f1函数中print(int(n) / 1024, 'kb')出错了,原因是ValueError: invalid literal for int() with base 10: 'l'
抛出错误
- 错误也是class,捕获一个错误就是捕获到该class的一个实例。在程序中也可以由我们自己抛出错误,需要使用关键字——raise:
def fun(i): if int(i) % 2: raise ValueError('%s是奇数' % i) def main(): try: i = input('请输入一个偶数:') fun(i) print('正确!') except ValueError as e: print('错误是:', e) main()
运行结果如下
请输入一个偶数:6 正确!
请输入一个偶数:7 错误是: 7是奇数
- 我们也可以 定义自己的错误类,选择好继承的父类,然后就能在程序中使用raise抛出:
class MyError(ValueError): pass def main(): try: raise MyError('抛出自定义的错误MyError!') except MyError as e: print(e) main()
运行结果:
抛出自定义的错误MyError!
-
我们在使用except捕获到错误后也可以再往上抛:
except ValueError as e: print(e) raise
raise语句在不带参数时是将错误e原样上抛,但我们也可以转换成另一种错误上抛:
def main(): try: re = 10 / 0 except ZeroDivisionError as e: raise ValueError('除数为零') main()
运行结果为:
Traceback (most recent call last): File "C:/Users/Whisky/.spyder-py3/temp.py", line 7, in <module> main() File "C:/Users/Whisky/.spyder-py3/temp.py", line 6, in main raise ValueError('除数为零') ValueError: 除数为零
调试
- 一种最简单的方法就是使用print()函数直接打印出变量的值进行查看。
- 使用断言(assert):
s = input() i = int(s) assert i != 0, 'i的值为0' #在此处,我们断言i的值不为零,不然后面的程序就会出错 rs = 1024 / i print(rs)
若断言的语句不成功,就会抛出AssertionError错误:
File "C:/Users/Whisky/.spyder-py3/temp.py", line 4, in <module> assert i != 0, 'i的值为0' AssertionError: i的值为0
若使用python -O ×××.py运行python程序就会忽略代码中的断言,将其视为pass。
-
使用logging。logging一共有四种模式:DEBUG, INFO, WARNING, ERROR,优先级依次递增。如果将logging配置为INFO模式,那么可以这样使用:
import logging logging.basicConfig(level = logging.INFO) s = '0' n = int(s) logging.info('n = %d' % n) #打印出n的信息 print(10 / n)
除了可以在控制台输出信息外,还可以将信息输出到文件中。当指定level=INFO后logging.debug()就不起作用了,同理当指定level=WARNING后logging.info()和logging.debug()就不起作用了,指定level=ERROR后logging.debug(),logging.info()以及logging.warning()都不起作用了。
- 还可以使用pdb来对程序进行调试,我们需要导入pdb包,使用pdb.set_trace()可以给程序设置断点,当程序运行到端点时就会暂停,然后进入调试模式。在调试模式下,命令l是用来查看代码,命令n是执行下一条语句,命令p ×××(变量名)是查看变量,命令c是直接执行到下一个断点处,命令q是退出调试模式:
import pdb s1 = '1024' pdb.set_trace() s2 = '0' a = int(s1) b = int(s2) print(a / b)
> c:\users\whisky\.spyder-py3\temp.py(6)<module>() 4 s1 = '1024' 5 pdb.set_trace() ----> 6 s2 = '0' 7 a = int(s1) 8 b = int(s2) ipdb> l 1 # -*- coding: utf-8 -*- 2 import pdb 3 4 s1 = '1024' 5 pdb.set_trace() ----> 6 s2 = '0' 7 a = int(s1) 8 b = int(s2) 9 print(a / b) 10 11 ipdb> p s1 '1024' ipdb> n > c:\users\whisky\.spyder-py3\temp.py(7)<module>() 5 pdb.set_trace() 6 s2 = '0' ----> 7 a = int(s1) 8 b = int(s2) 9 print(a / b) ipdb> p s2 '0' ipdb> q Traceback (most recent call last): File "E:\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 110, in execfile exec(compile(f.read(), filename, 'exec'), namespace) File "C:/Users/Whisky/.spyder-py3/temp.py", line 7, in <module> a = int(s1) File "C:/Users/Whisky/.spyder-py3/temp.py", line 7, in <module> a = int(s1) File "E:\Anaconda3\lib\bdb.py", line 88, in trace_dispatch return self.dispatch_line(frame) File "E:\Anaconda3\lib\bdb.py", line 113, in dispatch_line if self.quitting: raise BdbQuit BdbQuit
- 最便捷高效的当然是使用IDE中的调试工具了,这里不做介绍。
单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。先定义一个简单的类Dog:
class Dog():
def __init__(self, name, age):
self.name = name
self.age = age
def get_age(self):
if self.age > 30:
raise ValueError()
return self.age
接下来编写测试单元,需要导入unittest模块,我们选择继承于unittest.TestCase:
from test1 import Dog
import unittest
class TestA(unittest.TestCase):
def test_init(self):
d = Dog('Dolly', 10)
self.assertEqual(d.name, 'Dolly')
self.assertTrue(d.age < 30)
def test_attr(self):
d = Dog('Dolly', 10)
d.name = 'Jacy'
self.assertEqual(d.name, 'Jacy')
self.assertTrue(isinstance(d.name, str))
def test_attrerror(self):
d = Dog('Dolly', 40)
with self.assertRaises(AttributeError): #这里我们访问不存在的属性,所以期望抛出AttributeError错误
val = d.color
with self.assertRaises(ValueError): #这里我们期望能抛出ValueError错误
d.get_age()
像test_init()这样以test开头的方法就是测试方法,不以test开头的方法便不会被认为是测试方法,在测试时就不会执行。对于每一类测试,我们都需要编写一个test_×××()方法。通过unittest.TestCase内置的测试就能够断言输出是否符合我们的期望,最常用的断言比如assertEqual()、assertTrue()、assertRaises()等等。
测试单元的运行通常有两种方法。第一种,在测试单元模块最后添加以下两行代码就能够将测试单元作为普通的脚本运行:
if __name__ == '__main__':
unittest.main()
第二种,在命令行中输入python -m unittest ×××:
可以在单元测试中编写两个特殊的setUp()
和tearDown()
方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。