错误捕获
调试
单元测试
文档测试
1.错误捕获
程序运行时,很容易出现错误,为了增强系统的健壮性,错误处理是少不了的,例如下面这块代码一但运行,程序就会因出错而自动结束了
def f():
r = 10 / 0
print('result:', r)
f()
输出结果:
Traceback (most recent call last):
File "E:/python/project/test7/test7_1.py", line 15, in <module>
f()
File "E:/python/project/test7/test7_1.py", line 2, in f
r = 10 / 0
ZeroDivisionError: division by zero
但是在实际应用中,某些时候出错是被需求考虑在内的,例如一个新闻类app在浏览一篇新闻时,先去本地文件里查找有没有这篇文章的缓存,如果有就直接加载缓存,否则去网络下载,下载后先把这篇文章缓存到本地文件,下次再点进来时直接去文件里加载这篇文章,这样可以节省用户流量,不必每次都去网络下载。其中,查找本地文件缓存时,就可能出现FileNotFoundException,类似这种异常如果不捕获处理,程序就会直接退出,后面的去网络加载的逻辑也执行不了了。
Python中的捕获异常写法:
def f1():
try:
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')
f1()
输出结果:
except: division by zero
finally...
END
except就相当于java的catch,一个try里可以有多个except,不过要注意,所有的Exception都是class,并继承自BaseException,会捕获其自身及其子类异常,所以要把范围小的写在上面,否则下面的永远也捕获不到异常了,有一个和java不一样的地方是,Python里的except后面可以接else
def f1():
try:
r = 10 / 1
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
else:
print('no error')
finally:
print('finally...')
print('END')
f1()
输出结果:
result: 10.0
no error
finally...
END
try…except可以跨跃多层调用:
def f():
r = 10 / 0
print('result:', r)
def f2():
try:
f()
except Exception as e:
print('except:', e)
f2()
输出结果:
except: division by zero
f方法里的错误未捕获,在f2方法里try…except也会捕获到。
自己抛出错误:
# 自定义一个error类型
class VError(ValueError):
pass
def f3(i):
if i == 0:
raise VError('Invalid value:%d' % i)
return 10 / i
f3(0)
另一种错误处理方式:
def f4():
try:
r = 10 / 0
print('result:', r)
except Exception as e:
print('f4', e)
raise
finally:
print('finally')
print('end')
def f5():
try:
f4()
except Exception as e:
print('f5', e)
f5()
输出结果:
f4 division by zero
finally
f5 division by zero
在f4方法的第七行,打印错误后又把错误抛出了,如果别的方法调用f4,就要捕获这个抛出的错误,否则就会出异常,就算捕获了错误,第10行的end代码也不会执行了。
raise语句如果不带参数就会把错误原样抛出,另外,raise可以把一个错误转化成另一个类型抛出:
def f4():
try:
r = 10 / 0
print('result:', r)
except Exception as e:
print('f4', e)
raise VError('000 error!')
finally:
print('finally')
print('end')
def f5():
try:
f4()
except Exception as e:
print('f5', e)
f5()
输出结果:
f4 division by zero
finally
f5 000 error!
2.调试
import logging
logging.basicConfig(level=logging.DEBUG) # 设置打印的Log级别
logging.debug('n = %d' % 1) #输出Log
输出结果:
DEBUG:root:n = 1
断点调试使用IntelliJ IDEA和java调试方式一样。
3.单元测试
import unittest
from test7.test7_3 import Dict
class TestDict(unittest.TestCase):
def test_init(self):
d = Dict(a=1, b='test')
self.assertEqual(d.a, 1)
self.assertEqual(d.b, 'test')
self.assertTrue(isinstance(d, dict))
def test_keyerror(self):
d = Dict()
with self.assertRaises(KeyError): # 调用d['key']期待抛出KeyError异常,
value = d['key']
def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError): # 调用d['key']期待抛出AttributeError异常,
value = d.key
if __name__ == '__main__': # 运行该.py文件里所有的test
unittest.main()
可以通过在某个方法名上右键debug,这样会只debug这一个方法,也可以通过加上24和25两行代码,然后运行当前.py文件,一次运行所有的测试。
setUp和tearDown
class TestDict(unittest.TestCase):
def setUp(self):
print('before...')
def tearDown(self):
print('after...')
在上面的类里加上这两个方法后的运行结果是
before...
after...
before...
after...
before...
after...
会在每个方法运行之前执行before,在运行之后执行after,例如这些方法里测试数据库数据的话,就需要先连接数据库,测试完后要释放数据库连接,那么连接和断开连接就可以分别在setUp和tearDown方法里做。
4.文档测试
当编写注释时,可以定成这样:
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/0)
if __name__=='__main__':
import doctest
doctest.testmod()
输出结果:
**********************************************************************
File "E:/python/project/test7/test7_4.py", line 9, in __main__.abs
Failed example:
abs(-1)
Exception raised:
Traceback (most recent call last):
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python35\lib\doctest.py", line 1321, in __run
compileflags, 1), test.globs)
File "<doctest __main__.abs[1]>", line 1, in <module>
abs(-1)
File "E:/python/project/test7/test7_4.py", line 14, in abs
return n if n >= 0 else (-n/0)
ZeroDivisionError: division by zero
**********************************************************************
1 items had failures:
1 of 3 in __main__.abs
***Test Failed*** 1 failures.
注释中的这些示例代码可以在Python交互环境中执行,并输出注释中的结果,如果结果和文档中显示的不一样,说明调用有错,通过doctest.testmod()来开启文档测试。
利用这种方式测试前面的Dict类:
class Dict(dict):
'''
Simple dict but also support access as x.y style.
>>> d1 = Dict()
>>> d1['x'] = 100
>>> d1.x
100
>>> d1.y = 200
>>> d1['y']
200
>>> d2 = Dict(a=1, b=2, c='3')
>>> d2.c
'3'
>>> d2['empty']
Traceback (most recent call last):
...
KeyError: 'empty'
>>> d2.empty
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'empty'
'''
def __init__(self, **kw):
super().__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
if __name__ == '__main__':
import doctest
doctest.testmod()
运行后没有任何结果,因为没有错误,代码都是按注释中的示例规则来执行和输出的,这时如果把setattr()方法去掉的话,第9行的设置就不会生效,第10行就不会按照文档要求走了,此时会输出这样的错误:
**********************************************************************
File "E:/python/project/test7/test7_4.py", line 26, in __main__.Dict
Failed example:
d1['y']
Exception raised:
Traceback (most recent call last):
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python35\lib\doctest.py", line 1321, in __run
compileflags, 1), test.globs)
File "<doctest __main__.Dict[4]>", line 1, in <module>
d1['y']
KeyError: 'y'
**********************************************************************
1 items had failures:
1 of 9 in __main__.Dict
***Test Failed*** 1 failures.