(一)错误处理
在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码,这样,就可以知道是否有错,以及出错的原因。所以高级语言通常都内置了一套try...except...finally...
的错误处理机制:
try
# try
try:
print("try...")
n = 20 / 0
print("result:", n)
except ZeroDivisionError as e:
print("except:", e)
finally:
print("finally...")
print('END')
try...
except: division by zero
finally...
END
当某些代码可能会出错时,就可用try
来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except
句块,执行完except
后,如果有finally
语句块,则执行finally
语句块,至此,执行完毕。Python的错误其实也是class,所有的错误类型都继承自BaseException
,所以在使用except
时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。
使用try...except
捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()
调用f()
,f()
调用b()
,结果b()
出错了,这时,只要main()
捕获到了,就可以处理。不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以。
def f(s):
return 10 / int(s)
def b(s):
return f(s) * 2
def main():
try:
b('0')
except Exception as e:
print('Error:',e)
finally:
print('finally...')
main()
调用栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置。
记录错误
如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
Python内置的logging
模块可以非常容易地记录错误信息:
# err_logging.py
import logging
def f(s):
return 10 / int(s)
def b(s):
return f(s) * 2
def main():
try:
b('0')
except Exception as e:
logging.exception(e)
main()
print('END')
ERROR:root:division by zero
Traceback (most recent call last):
File "demo.py", line 40, in main
b('0')
File "demo.py", line 36, in b
return f(s) * 2
File "demo.py", line 33, in f
return 10 / int(s)
ZeroDivisionError: division by zero
END
通过配置,logging可以把错误记录到日志文件里。
抛出错误
因为错误是class,捕获一个错误就是捕获到该class的一个实例。如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例。
class FoErr(ValueError):
pass
def fo(s):
n = int(s)
if n == 0:
raise FoErr('invalid value:%s' % s)
return 10 / n
fo(0)
Traceback (most recent call last):
File "demo.py", line 62, in <module>
fo(0)
File "demo.py", line 58, in fo
raise FoErr('invalid value:%s' % s)
__main__.FoErr: invalid value:0
(二)调试
程序有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):
...
ZeroDivisionError: division by zero
不建议使用print()
- 第二种:断言
assert
assert
的意思是,表达式n != 0
应该是True
,否则,根据程序运行的逻辑,后面的代码肯定会出错。
如果断言失败,assert
语句本身就会抛出AssertionError
def foo(s):
n = int(s)
assert n != 0, 'n = 0'
return 10 / n
def main():
foo('0')
main()
Traceback (most recent call last):
...
AssertionError: n = 0
启动Python解释器时可以用-O
参数来关闭assert
,关闭后,所有的assert
语句当成pass
来看。
- 第三种:
logging
logging
不会抛出错误,而且可以输出到文件
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n=%d' % n)
print(10 / n)
INFO:root:n=0
Traceback (most recent call last):
File "err.py", line 31, in <module>
print(10 / n)
ZeroDivisionError: division by zero
logging的好处,允许你指定记录信息的级别,有debug,info,warning,error等几个级别。可以放心地输出不同级别的信息,不用删除,最后统一控制输出哪个级别的信息。通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
- 第四种:IDE设置断点
单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
unittest
模块:提供一整套的测试框架单元测试框架
-
测试脚手架
test fixture 表示为了开展一项或多项测试所需要进行的准备工作,以及所有相关的清理操作。举个例子,这可能包含创建临时或代理的数据库、目录,再或者启动一个服务器进程。 -
测试用例
一个测试用例是一个独立的测试单元。它检查输入特定的数据时的响应。 unittest 提供一个基类: TestCase ,用于新建测试用例。 -
测试套件
test suite 是一系列的测试用例,或测试套件,或两者皆有。它用于归档需要一起执行的测试。 -
测试运行器(test runner)
test runner 是一个用于执行和输出测试结果的组件。这个运行器可能使用图形接口、文本接口,或返回一个特定的值表示运行测试的结果。
1、TestCase类:
多函数多用例测试:
import unittest # 导入测试模块
def showMsg(msg):
return '%s' % msg
def do_divdie(a, b):
return a / b
def showTrue(flag):
return flag
class TestSomeFunc(unittest.TestCase): # 自定义测试类 继承unittest.TestCase
def testrun(self): # 自定义测试方法
self.assertEqual('MC', showMsg('MC'))
self.assertNotEqual('OK', showMsg('NO'))
self.assertTrue(do_divdie(1, 2)) # 测试结果值是否为True
self.assertIs(showTrue(False), False) # 测试预期值与函数结果是否一致
self.assertIs(int(do_divdie(1, 2)), 1) # 测试预期值与函数结果是否一致
if __name__ == '__main__':
unittest.main() # 调用mian方法自动执行测试用例
测试结果:
F # 出错信息提醒
======================================================================
FAIL: testrun (__main__.TestSomeFunc)
----------------------------------------------------------------------
Traceback (most recent call last):
File "单元测试.py", line 22, in testrun # 22行代码,执行结果和预期不一致
self.assertIs(int(do_divdie(1, 2)), 1)
AssertionError: 0 is not 1 #结果0不符合预期值1
----------------------------------------------------------------------
Ran 1 test in 0.000s # 测试所用时间
FAILED (failures=1) #一项测试失败
文档测试
doctest
对所编写的代码当做文档字符串进行读取,然后通过相应的测试规则,对所编写的代码进行自动测试。doctest
不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest
的注释提取出来。用户看文档的时候,同时也看到了doctest
。
- 1、嵌入代码测试
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(Dict, self).__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()
什么输出也没有。说明编写的doctest
运行都是正确的。将数字200
加上' '
,就会报错:
**********************************************************************
File "文档测试.py", line 10, in __main__.Dict
Failed example:
d1['y']
Expected:
200
Got:
'200'
**********************************************************************
1 items had failures:
1 of 9 in __main__.Dict
***Test Failed*** 1 failures.