错误与调试
到目前为止,我们并没有更多提及错误信息,但是我们在上课的例子和作业中肯定已经或多或少的遇到过不少了。我们平时遇到的错误信息可以分为两种:syntax error和exception , 按中文来说, 就是语法错误和异常。
语法错误
语法错误,也可以认为是解析时错误,这是在你学习Python过程中最有可能碰到的:
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
语法分析器指出错误行,然后显示一个小箭头, 指出检测到错误时最早的那个点。错误一般是由箭头所指的地方导致 (或者至少是此处被检测到): 在这个例子中, 错误是在print()函数这里被发现的, 因为在它之前少了一个冒号 (‘:’). 文件的名称与行号会被打印出来, 以便于你能找到一个脚本中导致错误的地方.
当然如果我们使用一些IDE,如pycharm进行开发,那么一般都会有提示。
异常
尽管语句或表达式语法上是没有问题的, 但是同样也会在尝试运行时导致一个错误. 在执行时探测到的错误就叫做exception, 也就是异常, 但它并不是致命的问题,这一类的异常我们是可以处理的。
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ZeroDivisionError: int division or modulo by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: Can't convert 'int' object to str implicitly
错误信息的最后一行指出发生了什么错误。异常也有不同的类型,异常类型做为错误信息的一部分显示出来:示例中的异常分别为:零除错误(ZeroDivisionError),命名错误(NameError)和类型错误(TypeError)。打印错误信息时,异常的类型作为异常的内置名显示。
我们可以在这里看到内置的异常,列出了内置异常和它们的含义。
异常处理
Python内置了一套try…except…finally…的错误处理机制。当我们认为某些代码可能会出错时,就可以使用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。我们看一个例子:
try:
print('try...')
r = '2' + 2
print('result:', r)
except TypeError as e:
print('except:', e)
finally:
print('finally...')
上面的代码在计算’2’ + 2时会产生一个类型错误:
try...
except: must be str, not int
finally...
TypeError,因此被执行。最后finally语句被执行。然后程序继续按照流程往下走。如果将‘2’改为2,那么就会输出如下结果:
try...
result: 4
finally...
由于没有错误发生,所以except语句块不会被执行,但是finally如果有,则一定会被执行(可以没有finally语句)。
在内置的异常我们可以看到很多异常类型,那么我们要知道一个try结果里except可以有很多个,用来捕获不同的类型。如:
try:
print('try...')
r = '2' + 2
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except TypeError as e:
print('TypeError:', e)
finally:
print('finally...')
此外,如果没有错误发生,可以在except语句块后面加一个else,当没有错误发生时,会自动执行else语句:
try:
print('try...')
r = '2' + 2
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except TypeError as e:
print('TypeError:', e)
else:
print('no error!')
finally:
print('finally...')
Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。因为except只命中一个,有点类似于if语句。比如:
try:
print('try...')
r = 4 + spam*3
print('result:', r)
except NameError as e:
print('NameError:', e)
except UnboundLocalError as e:
print('UnboundLocalError:', e)
else:
print('no error!')
finally:
print('finally...')
第二个except永远也捕获不到UnicodeError,因为UnboundLocalError是NameError的子类,如果有,也被第一个except给捕获了。
Python所有的错误都是从BaseException类派生的,常见的错误类型和继承关系看这里:
调用栈
使用try…except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用second(),second()调用first(),结果first()出错了,这时,只要main()捕获到了,就可以处理:
def first(s):
return s + 2
def second(s):
return first(s) * 2
def main():
try:
second('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')
main()
也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try…except…finally的麻烦。
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。
Traceback (most recent call last):
File "day7.py", line 24, in <module>
main()
File "day7.py", line 22, in main
second('0')
File "day7.py", line 19, in second
return first(s) * 2
File "day7.py", line 16, in first
return s + 2
TypeError: must be str, not int
我们从上往下可以看到整个错误的调用函数链,然后最后是出错信息。