目录
错误、调试和测试
错误处理
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
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'
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是奇数
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
main()
File "C:/Users/Whisky/.spyder-py3/temp.py", line 6, in main
raise ValueError('除数为零')
ValueError: 除数为零
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
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()都不起作用了。
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)()
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)()
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
a = int(s1)
File "C:/Users/Whisky/.spyder-py3/temp.py", line 7, in
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
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。先定义一个简单的类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()
方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。