异常处理和调试


本章概述:python基础——异常处理和调试


1、异常捕获

编程语言在程序运行的时候,难免会发生错误,特别是程序在调试的时候,如果程序在运行过程中发生错误,将会导致程序崩溃退出,但是很多时候我们并不希望程序退出,我们可能只是希望程序给我返回一个错误信息,然后程序能够继续运行,这时,我们就可以使用python的异常处理机制。Python中和异常相关的关键字有五个,分别是tryexceptelsefinallyraise

1
2
3
4
5
6
7
8
try:
print(a)
except NameError:
print("出现名字的异常了")
else:
print('没有异常时执行的代码')
finally:
print("结束")

在python中,将需要检测的语句放在try代码块中,try后可以跟多个except代码块来捕获不同的异常并进行处理,else代码块是检测的代码不发生异常时执行的语句,不管是否发生异常finally都会执行。上面的代码由于a没有定义,产生了NameError异常,我们在except捕获这个异常并且执行相关语句。

2、内置异常类型

python内置了大量的异常类型,下面列一下部分异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning

Python所有的异常都是BaseException的子类型,子类型中最常用的是Exception类型,他是常规异常类型的父类型,也就是说,我们程序运行碰到的大部分异常都是它的子类型。

3、自定义异常

当内置的异常类型不满足程序要求时,我们也可以自定义异常来满足我们的要求,自定义异常需要用到raise关键字,并且自定义异常也应该继承于Exception或其子类型。

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

class PwLen(Exception):
'''自定义异常类型'''
pass

def test(number):
if number<10:
raise PwLen('number不能小于10') #触发异常
return number

try:
t=test(9)
except PwLen as p:
print(p) #打印异常
else:
print(t)

上面的例子是简单的自定义异常,我们还可以在自定义异常类中做更多的操作以满足不同的异常。

4、调试

程序编写过程中,经常需要进行调试,已保证能得到我们想要的结果,出现bug时,有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的,因此我们需要有调试的手段来修复bug。

第一种调试的方法很简单也很粗暴,就是直接用print把可能出现的错误的值打印出来看看是不是正常,很多人都是直接用这种方法,包括我也是,在写脚本时经常用到print来打印变量,但是这种调试方法不好的地方就是一旦写print的地方太多的话,很混乱,并且不能一眼就看出bug所在,需要我们做判断。

第二种方法就是使用断言(assert),凡是用print来辅助查看的地方,都可以用断言(assert)来替代。

1
2
3
4
5
def N(n):
assert n!=0,'n不能为0'
print(n)

N(0) #这里会直接导致程序崩溃,并打印出“n不能为0”

assert判断某个表达式的值时,如果结果为True,则程序继续执行,如果结果为False,则报AssertionError错误。在编写程序的时候,养成习惯,尽量使用断言去调试,而不是选择print。在运行程序时,可以加-O选项来忽略assert语句。

第三种方法是通过logging模块来进行调试,和assert相比,这种方法不会抛出错误,而是输入到文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

import logging
import os

#获取当前文件路径,__file__表示当前文件的绝对路径
current_work_dir = os.path.dirname(__file__)

#更改日志输出的格式
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
#设置日志时间格式
DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"

logging.basicConfig(level=logging.DEBUG,filename=f'{current_work_dir}\log.txt',format=LOG_FORMAT,datefmt=DATE_FORMAT)

logging.log(logging.DEBUG, "This is a debug log.")
logging.log(logging.INFO, "This is a info log.")
logging.log(logging.WARNING, "This is a warning log.")
logging.log(logging.ERROR, "This is a error log.")
logging.log(logging.CRITICAL, "This is a critical log.")

'''
04/22/2022 10:33:25 AM - DEBUG - This is a debug log.
04/22/2022 10:33:25 AM - INFO - This is a info log.
04/22/2022 10:33:25 AM - WARNING - This is a warning log.
04/22/2022 10:33:25 AM - ERROR - This is a error log.
04/22/2022 10:33:25 AM - CRITICAL - This is a critical log.
'''

logging有5中日志级别,默认是WARNING级别,因此logging.basicConfig不指定级别的情况下,DEBUG和INFO级别的信息是不输出的,如果想要输出所有级别的日志,需要将level设置为DEBUG。在编写复杂的程序时,日志信息是不可缺少的。