GuoXin Li's Blog

Python_Error_Handing

字数统计: 988阅读时长: 4 min
2020/01/23 Share

Python 错误处理

try 来运行代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即 except 语句块,执行完 except 后,如果有 finally 语句块,则执行。

finally 如果有,则一定会被执行(finally 语句可以不设)

可以设多个 except 来捕获不同类型的错误

1
2
3
4
5
6
7
8
9
10
11
try:
print('try...')
r = 10 / int('a')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
finally:
print('finally...')
print('END')

此外,如果没有错误发生,可以在 except 语句块后面加一个 else ,当没有错误发生时,会自动执行 else 语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')
finally:
print('finally...')
print('END')

Python 的错误是 class, 所有的错误类型都继承自 BaseException, 所以在使用 except 时需要注意:它不但捕获该类型的错误,还把其他子类也一同捕获。

1
2
3
4
5
6
try:
foo()
except ValueError as e:
print('ValueError')
except UnicodeError as e:
print('UnicodeError')

第二个 except 永远也捕获不到UnicodeError,因为 UnicodeErrorValueError 的子类,如果有错误,也将会被第一个 except 给捕获。

常见错误类型和继承关系链接🔗

调用栈

如果错误没有被捕获,它就会一直往上抛,最后被 Python 解释器捕获,打印一个错误信息,然后程序退出:

1
2
3
4
5
6
7
8
9
10
def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
bar('0')

main()

执行,结果:

1
2
3
4
5
6
7
8
9
10
11
$ python3 err.py
Traceback (most recent call last):
File "err.py", line 11, in <module>
main()
File "err.py", line 9, in main
bar('0')
File "err.py", line 6, in bar
return foo(s) * 2
File "err.py", line 3, in foo
return 10 / int(s)
ZeroDivisionError: division by zero

由上往下,依次是错误的跟踪信息,由引发点,最终至错误产生的源头。

原因是:ZeroDivisionError: division by zero ,因此判断 10 / 0 出错。

记录错误

如果我们捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时让程序继续执行下去。

Python 的内置 logging 模块可以非常容易地记录错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import logging

def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
try:
bar('0')
except Exception as e:
logging.exception(e)

main()
print('END')

通过 logging.exception(e) 出错时,程序打印完错误信息后会继续执行,并正常退出:

1
2
3
4
5
6
7
8
9
10
11
$ python3 err_logging.py
ERROR:root:division by zero
Traceback (most recent call last):
File "err_logging.py", line 13, in main
bar('0')
File "err_logging.py", line 9, in bar
return foo(s) * 2
File "err_logging.py", line 6, in foo
return 10 / int(s)
ZeroDivisionError: division by zero
END

logging 还可以把错误记录到日志文件里,方便事后排查。

抛出错误

因为错误是 class,捕获一个错误就是捕获到该 class 的一个实例。

如果要抛出错误,首先根据需要,可以定义一个错误 class,选择好继承关系,然后,用 raise 语句抛出一个错误的实例。

1
2
3
4
5
6
7
8
9
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n == 0:
raise FooError('invalid value: %s' % s)
return 10 / n

foo('0')

执行,可以看到自定义的错误:

1
2
3
4
5
6
7
$ python3 err_raise.py
Traceback (most recent call last):
File "err_throw.py", line 11, in <module>
foo('0')
File "err_throw.py", line 8, in foo
raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0

继续往上抛错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def foo(s):
n = int(s)
if n==0:
raise ValueError('invalid value: %s' % s)
return 10 / n

def bar():
try:
foo('0')
except ValueError as e:
print('ValueError!')
raise

bar()

bar( ) 函数中,已经捕获了错误,但是打印了一个 ValueError 后,又把错误通过 raise 语句抛出去了。

这样:捕获错误目的只是记录一下,便于后续追踪。但是由于当前函数不知道该如何处理该错误,所以,最好的方式是继续往上抛,让顶层调用者去处理。

raise 语句如果不带参数,就会把当前错误原样抛出。

此外,在 exceptraise 一个 Error,还可以把一种类型的错误转化成另一种类型:

1
2
3
4
try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
CATALOG
  1. 1. Python 错误处理
    1. 1.1. 调用栈
    2. 1.2. 记录错误
    3. 1.3. 抛出错误