在程序运行过程中,总会遇到各种各样的错误。
有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这种错误我们通常称之为bug,bug是必须修复的。
有的错误是用户输入造成的,比如让用户输入email地址,结果得到一个空字符串,这种错误可以通过检查用户输入来做相应的处理。
还有一类错误是完全无法在程序运行过程中预测的,比如写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并退出。
错误处理
try...except...finally
当我们认为某些代码可能会出错时,就可以用try
来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except
语句块,执行完except
后,如果有finally
语句块,则执行finally
语句块,至此,执行完毕。
1 #!/usr/bin/python3 2 3 try: 4 r = 10 / 0 5 print ('result:',r) 6 except ZeroDivisionError as e: 7 print ('除数不能为零 ',e) 8 finally: 9 print ('--------') 10 print ('END')
如果没有错误发生,可以在except
语句块后面加一个else
,当没有错误发生时,会自动执行else
语句:
1 #!/usr/bin/python3 2 3 try: 4 r = 10 / int('2') 5 print ('result:',r) 6 except ZeroDivisionError as e: 7 print ('除数不能为零 ', e) 8 except ValueError as e: #int()可能抛出ValueError 9 print ('ValueError', e) 10 else: 11 print ('No Error!') 12 finally: 13 print ('--------') 14 print ('END')
Python的错误其实也是class,所有的错误类型都继承自BaseException
,所以在使用except
时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。
常见的错误类型和继承关系 https://docs.python.org/3/library/exceptions.html#exception-hierarchy
使用try...except
捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()
调用foo()
,foo()
调用bar()
,结果bar()
出错了,这时,只要main()
捕获到了,就可以处理:
1 #!/usr/bin/python3 2 3 def foo(s): 4 return 10 / s 5 6 def bar(s): 7 return foo(s)*2 8 9 def main(): 10 try: 11 bar('0') 12 except Exception as e: 13 print ('Error', e) 14 finally: 15 print ('---------------') 16 17 main()
Python中的异常分类
1 NameError #尝试访问一个未声明的变量(没有初始化) 2 ZeroDivisionError #除数为零 3 SyntaxError #解释器语法错误,唯一不是在运行时发生的异常,在编译时发生,它代表Python代码中有一个不正确的结构 4 IndexError #请求的索引超出序列范围 5 KeyError #请求一个不存在的字典键 6 IOError #输入输出错误:(1)打开一个不存在的文件(2)文件写入时,操作和模式不匹配(3)权限问题导致 7 TypeError #类型错误,如'2'+2,类型不相同不能相加 8 AttributeError #尝试访问未知的对象属性,即对象根本没有定义这一属性却尝试访问它 9 StopIteration #迭代器没有更多的值 10 RuntimeError #一般的运行时错误 11 SystemError #一般的解释器系统错误 12 ValueError #传入无效的参数
调用栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。
1 #!/usr/bin/python3 2 3 def foo(s): 4 return 10 / int(s) 5 6 def bar(s): 7 return foo(s)*2 8 9 def main(): 10 bar('0') 11 12 main()
可以看到,问题的源头追溯到第四行代码,第四行代码,除数不能为零
记录错误
捕获错误,打印错误堆栈,同时让程序继续执行
1 #!/usr/bin/python3 2 3 import logging 4 5 def foo(s): 6 return 10 / int(s) 7 8 def bar(s): 9 return foo(s)*2 10 11 def main(): 12 try: 13 bar('0') 14 except Exception as e: 15 logging.exception(e) 16 17 main() 18 print('END')
同样是出错,但程序打印完错误信息后会继续执行,并正常退出:
抛出错误
如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise
语句抛出一个错误的实例:
1 #!/usr/bin/python3 2 3 class FooError(ValueError): #选择好继承关系 4 pass 5 6 def foo(s): 7 n = int(s) 8 if n == 0: 9 raise FooError('无效的值:%s' % s) 10 return 10 / n 11 12 foo('0')
只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError
,TypeError
),尽量使用Python内置的错误类型。
调试
程序能一次写完并正常运行的概率很小,总会有各种各样的bug需要修正。有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的,因此,需要一整套调试程序的手段来修复bug。
断言 assert
把可能有问题的变量打印出来看看
1 #!/usr/bin/python3 2 3 def foo(s): 4 n = int(s) 5 assert n != 0, 'n is zero!' 6 return 10 / n 7 8 def main(): 9 foo('0') 10 11 main()
assert
的意思是,表达式 n != 0
应该是True
,True的话继续执行,否则,根据程序运行的逻辑,后面的代码肯定会出错。
如果断言失败,assert
语句本身就会抛出AssertionError
:
logging
和assert
比,logging
不会抛出错误,而且可以输出到文件,可指定记录信息的级别(低到高:debug info warning error)当指定 level = WARNING后 debug和info就不起作用,即可以输出不同级别的信息
1 #!/usr/bin/python3 2 3 import logging 4 logging.basicConfig(level = logging.INFO) 5 6 s = '0' 7 n = int(s) 8 logging.info('n = %d' % n) 9 print (10 / n)
pdb
-m pdb 让程序以单步方式运行,可以随时查看运行状态。
1 #!/usr/bin/python3 2 3 s = '0' 4 n = int(s) 5 print (10 / n)
l 查看代码 n 单步 p 变量名 查看变量 q结束调试