Python学习全部目录:
【Python学习】基础学习(一):变量与运算、数据类型、函数、类、模块
【Python学习】基础学习(二):文件管理与I/O编程
【Python学习】基础学习(三):异常处理、调试和测试
- 异常处理、调试和测试
- 控制异常try-except
- 调试器pdb
- Unittest单元测试
- 异常/警告名称列表
异常处理、调试和测试
控制异常try-except
即使自己的的代码没有错误,但是有时也难以避免潜在的错误,比如说物理环境出错,CPU资源不够,调用的服务失败。
在写代码的时候,考虑如何对捕捉潜在的错误,预先写好对于可能错误的处理。
- try-except
比如处理文件时候,如果读写不存在的文件就会报错:
withopen('no_file.txt','r')asf:print(f.read())# FileNotFoundError: [Errno 2] No such file or directory: 'no_file.txt'我们需要注意的就是表示异常类型的报错关键词FileNotFoundError,需要手动捕捉这个错误并且进行处理
try:withopen('no_file.txt','r')asf:print(f.read())exceptFileNotFoundErrorase:print(e)withopen('no_file.txt','w')asf:f.write('This is a file for test.')print('new file "no_file.txt" has been written.')有时候程序执行某种功能会报多种不同的异常,如果异常的处理方案相同,可以在except中写多个异常种类,会按照正常的执行顺序, 一次检测异常,报出第一个遇到的异常:
dic={'name':'Bob','age':20}l=[1,2,3]try:male=dic['gender']l[3]=4except(KeyErrororIndexError)ase:print('ker or index error for: ',e)# ker or index error for: 'gender'- try-except-except
如果想让多种异常分开处理,就需要写两个except:
try:v=dic['gender']l[3]=4exceptKeyErrorase:print('key error for:',e)dic['gender']='Female'exceptIndexErrorase:print('index error for:',e)l.append(4)print(dic)print(l)# key error for: 'gender'# {'name': 'Bob', 'age': 20, 'gender': 'Female'}# [1, 2, 3]- try-except-else
这个模式在except处理报错情况:
lis=[1,2,3]try:print(lis[3])exceptIndexErrorase:print(e)lis.append(4)print(lis[3])else:print('now is in else.')# list index out of range# 4在else处理没有报错的情况:
lis=[1,2,3,4]try:print(lis[3])exceptIndexErrorase:print(e)lis.append(4)print(lis[3])else:print('now is in else.')# 4# now is in else.- try-except-finally
不管有没有错误,都会执行到finally内容
有报错:
lis=[1,2,3]try:print(lis[3])exceptIndexErrorase:print(e)lis.append(4)print(lis[3])finally:print('reach finally.')# list index out of range# 4# reach finally.没有报错:
lis=[1,2,3,4]try:print(lis[3])exceptIndexErrorase:print(e)lis.append(4)print(lis[3])finally:print('reach finally.')# 4# reach finally.主要用在不管有没有报错,都想让程序继续执行,不处理任何异常:
try:dd=ddddfinally:print('I know there is an error, just ignore it.')# I know there is an error, just ignore it.# NameError: name 'dddd' is not defined- 记录错误
使用python内置的logging模块,就可以记录并打印输出错误信息,同时可以让程序继续执行,而不是出错就退出执行:
importlogging lis=[1,2,3]deflogerror():try:print(lis[3])exceptExceptionase:# Exception是基类logging.exception(e)logerror()print('main')# IndexError: list index out of range# main- raise手动触发异常
主动抛出异常,在错误的地方中断程序并输出清晰地错误信息
defno_negative(num):ifnum<0:raiseValueError('No Negative!')returnnum no_negative(-1)# Traceback (most recent call last):# ValueError: No Negative!这种raise的用法,更常用在网络请求、读取数据库时,如果有raise写入到log中就会非常清晰易懂。
总结:做一个小练习
# 运行下面的代码,根据异常信息进行分析,定位出错误源头,并修复:fromfunctoolsimportreducedefstr2num(s):returnint(s)defcalc(exp):ss=exp.split('+')ns=map(str2num,ss)returnreduce(lambdaacc,x:acc+x,ns)defmain():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# Traceback (most recent call last):# File "/home/bupt/python/python_basic.py", line 17, in <module># main()# File "/home/python/python_basic.py", line 14, in main# r = calc('99 + 88 + 7.6')# File "/home/python/python_basic.py", line 9, in calc# return reduce(lambda acc, x: acc + x, ns)# File "/home/python/python_basic.py", line 4, in str2num# return int(s)# ValueError: invalid literal for int() with base 10: ' 7.6'从上往下分析错误信息,最后发现是str2num(s)函数调用的int(s)出错,不能转换7.6这个浮点数
调试器pdb
(以下为不依赖IDE的调试方式)
启动python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态:
s='0'n=int(s)print(10/n)配置参数-m pdb命令行执行python程序:python -m pdb python_basic.py
使用n单步执行,使用p 变量名查看变量的值
>/home/python/python_basic.py(1)<module>()->s='0'(Pdb)(Pdb)n# 使用 n 单步执行>/home/python/python_basic.py(2)<module>()->n=int(s)(Pdb)n>/home/python/python_basic.py(3)<module>()->print(10/ n)(Pdb)p s# 使用 p 查看变量'0'(Pdb)n ZeroDivisionError: division by zero>/home/python/python_basic.py(3)<module>()->print(10/ n)(Pdb)n --Return--可以import pdb,使用pdb.trace()设置断点:
importpdb s='0'n=int(s)pdb.set_trace()print(10/n)使用python python_basic.py运行程序,会自动在断点处暂停进入pdb调试环境,使用命令p查看变量,使用命令c继续运行:
$ python python_basic.py>/home/bupt/python/python_basic.py(5)<module>()->pdb.set_trace()(Pdb)p s'0'(Pdb)p n0(Pdb)c Traceback(most recent call last): File"/home/bupt/python/python_basic.py", line6,in<module>print(10/ n)~~~^~~ ZeroDivisionError: division by zeroUnittest单元测试
运行整套程序,检查哪里出错的测试方法只适合:小型项目/项目功能之间关联少的少量功能项目,否则,使用单元测试是更好的测试方法。
单元测试就是不直接测试全套程序逻辑,而是将小功能模块拆开逐个测试。在Python中,常用unittest测试框架做单元测试。
在class中继承unittest.TestCase,编写函数,以test开头的TestXxx类被认为是测试类,test_xxx方法被认为是测试方法
在__main__函数中执行unittest.main(),这个“测试运行器”会自动发现并执行定义的测试类中所有的测试方法:
importunittestclassTestFunc(unittest.TestCase):deftest_example(self):self.assertEqual(2,divide(2,1))self.assertEqual(2,divide(-2,-1))# self.assertEqual(-1, divide(-2, 0))defdivide(a,b):returna/bif__name__=='__main__':unittest.main()# .# --------------------# Ran 1 test in 0.000s# OK如果测试不通过,会报错:
self.assertEqual(-1,divide(-2,0))# return a / b# ~~^~~# ZeroDivisionError: division by zero# ---------------------------------------------------------------------# Ran 1 test in 0.000s# FAILED (errors=1)- unittest规范
可以先写unittest,然后再开发功能,类似于先设立目标,然后实现目标:
比如想要:输入 1 返回 2,输入 -1 返回 3,输入其他任何数,返回 1。可以像下面一样先把test_func部分写好:
classTestFunc(unittest.TestCase):deftest_example(self):# 期望实现的功能self.assertEqual(2,my_func(1))self.assertEqual(3,my_func(-1))foriinrange(-100,100):ifi==1ori==-1:continueself.assertEqual(1,my_func(i))defmy_func(num):if__name__=='__main__':unittest.main()测试文件要与原本的功能文件分开,测试文件以_test.py结尾,引入from all_my_func import my_func功能模块函数以进行测试,
可以同时测试多个功能模块:
defmy_func1(a):ifa==1:return2elifa==-1:return3else:return1defmy_func2(letter):ifletter=='yes':returnTrueelse:raiseValueError("Only YES!")classTestFunc(unittest.TestCase):deftest_func1(self):self.assertEqual(2,my_func1(1))self.assertEqual(3,my_func1(-1))foriinrange(-100,100):ifi==1ori==-1:continueself.assertEqual(1,my_func1(i))deftest_func2(self):self.assertTrue(my_func2('yes'))withself.assertRaises(ValueError):# 报错才能通过testmy_func2("nononono")if__name__=="__main__":unittest.main()# ..# ----------------------------------------------------------------------# Ran 2 tests in 0.000s# OK通常将测试代码写在test.py文件中,在命令行使用python指令执行测试:python -m unittest test.py
如果写出了很多的功能测试,但有时只想执行某一个test,可以进行如下替换:
但是这样写灵活性太差:
# unittest.main()suite=unittest.TestSuite()suite.addTest(TestFunc('test_func1'))unittest.TextTestRunner.run(suite)# .# ----------------------------------------------------------------------# Ran 1 test in 0.000s# OK不如直接命令行执行对应参数命令来执行不同的test,灵活性更强:python -m unittest testmodule.testclass.test_method
setUp()&tearDown()
在使用unittest进行单元测试时,可能需要前置准备和后置清理:
-> 测试前需要准备数据,连接数据库,打开文件;
-> 测试后需要清理数据,关闭数据库连接,删除临时文件
为了避免在每一个测试方法中重复写这些准备和清理的代码,unittest提供了两个钩子方法:
setUp()
在每个测试方法运行之前都会被自动调用
用于创建测试所需要的环境或数据准备tearDown()
在每个测试方法运行之后都是被自动调用
用于释放资源,清楚测试过程中产生的数据或恢复环境状态
importunittestclassTestExample(unittest.TestCase):defsetUp(self):print('setUp')returnsuper().setUp()deftearDown(self):print('tearDown')returnsuper().tearDown()deftest_fun1(self):print('test func 1')self.assertTrue(True)deftest_fun2(self):print('test func 2')self.assertEqual(1+1,2)if__name__=='__main__':unittest.main()# setUp# test func 1# tearDown# .setUp# test func 2# tearDown# .# ------------------------------------------# Ran 2 tests in 0.000s# OK数据库连接场景:
defsetUp(self):self.conn=db.connect()self.cursor=self.conn.cursor()deftearDown(self):self.cursor.close()self.conn.close()临时文件创建与删除:
defsetUp(self):self.file=open('temp.txt','w')deftearDown(self):self.file.close()os.remove('temp.txt')unittest断言方法列表
| 断言方法 | 含义 |
|---|---|
assertEqual(a, b) | 判断a == b |
assertNotEqual(a, b) | 判断a != b |
assertTrue(condition) | 判断condition 是 True |
assertFalse(condition) | 判断condition 是 False |
assertGreater(a, b) | 判断a > b |
assertGreaterEqual(a, b) | 判断a >= b |
assertLess(a, b) | 判断a < b |
assertLessEqual(a, b) | 判断a <= b |
assertIs(a, b) | 判断a is b(是否是同一个对象) |
assertIsNot(a, b) | 判断a is not b(不是同一个对象) |
assertIsNone(a) | 判断a is None |
assertIsNotNone(a) | 判断a is not None |
assertIn(a, b) | 判断a in b(a 是否在 b 中) |
assertNotIn(a, b) | 判断a not in b(a 不在 b 中) |
assertRaises(error) | 判断是否抛出了指定的异常(常与with一起使用) |
异常/警告名称列表
| 异常名称 | 描述 |
|---|---|
| BaseException | 所有异常的基类 |
| SystemExit | 解释器请求退出 |
| KeyboardInterrupt | 用户中断执行(通常是 Ctrl+C) |
| Exception | 常规错误的基类 |
| StopIteration | 迭代器没有更多的值 |
| GeneratorExit | 生成器(generator)发生异常来通知退出 |
| StandardError(Python 3 中已移除) | 所有内建标准异常的基类 |
| ArithmeticError | 所有数值计算错误的基类 |
| FloatingPointError | 浮点计算错误 |
| OverflowError | 数值运算超出最大限制 |
| ZeroDivisionError | 除以零或取模为零(适用于所有数据类型) |
| AssertionError | 断言语句失败 |
| AttributeError | 对象没有该属性 |
| EOFError | 没有内建输入,到达 EOF 标记 |
| EnvironmentError(已合并进 OSError) | 操作系统错误的基类 |
| IOError(Python 3中与 OSError 合并) | 输入/输出操作失败 |
| OSError | 操作系统错误 |
| WindowsError(仅限 Windows,Python 3 已移除) | 系统调用失败 |
| ImportError | 导入模块/对象失败 |
| LookupError | 无效数据查询的基类 |
| IndexError | 序列中没有此索引 |
| KeyError | 映射中没有这个键 |
| MemoryError | 内存溢出错误(不是致命错误) |
| NameError | 未声明或未初始化的对象(变量不存在) |
| UnboundLocalError | 访问未初始化的本地变量 |
| ReferenceError | 弱引用(weakref)试图访问已被回收的对象 |
| RuntimeError | 一般的运行时错误 |
| NotImplementedError | 尚未实现的方法 |
| SyntaxError | Python 语法错误 |
| IndentationError | 缩进错误 |
| TabError | Tab 与空格混用导致缩进错误 |
| SystemError | 解释器系统错误 |
| TypeError | 对类型无效的操作 |
| ValueError | 传入无效参数(类型对但值不对) |
| UnicodeError | Unicode 相关错误的基类 |
| UnicodeDecodeError | Unicode 解码错误 |
| UnicodeEncodeError | Unicode 编码错误 |
| UnicodeTranslateError | Unicode 转换错误 |
| 警告名称 | 描述 |
|---|---|
| Warning | 警告的基类 |
| DeprecationWarning | 关于被弃用特性的警告 |
| PendingDeprecationWarning | 将来某个版本会弃用的特性警告 |
| FutureWarning | 构造将来语义可能会改变的警告 |
| RuntimeWarning | 可疑的运行时行为警告 |
| SyntaxWarning | 可疑的语法警告 |
| UserWarning | 用户代码生成的警告 |
| OverflowWarning(Python 2 使用,Python 3 已移除) | 自动转为 long 类型的旧警告 |