目录

在程序运行过程中,总会遇到各种各样的错误。
有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这种错误我们通常称之为
Bug
,而 Bug 是必须修复的。有的错误是用户输入造成的,比如让用户输入 email 地址,结果得到一个空字符串,这种错误可以通过检查用户输入来做相应的处理。
还有一类错误是完全无法在程序运行过程中预测的,比如写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并推出。
Python 内置了一套 异常处理
机制,来帮助我们进行错误处理。
除了异常处理外,我们也需要跟踪程序的执行,查看变量的值是否正确,这个过程称为 调试
。Python 的 pdb 可以让我们但不方式执行代码。
最后,编写 测试
也很重要。有了良好的测试,就可以在程序修改后反复运行,确保程序输出符合我们编写的测试。
一、错误处理
1)错误处理介绍
在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码。这样,就可以知道是否有错,以及出错的原因。在操作系统提供的调用中,返回错误码非常常见。比如打开文件的函数 open()
,成功时返回文件描述符(就是一个整数),出错时返回 -1
。
用错误码来表示是否出错十分不便,因为函数本身应该返回的正常结果和错误码混在一起,造成调用者必须用大量的代码来判断是否出错:
def foo():
r = some_function()
if r == (-1):
return (-1)
# do something
return r
def bar():
r = foo()
if r == (-1):
print('Error')
else:
pass
一旦出错,还要一级一级上报,直到某个函数可以处理该错误(比如,给用户输出一个错误信息)。
所以高级语言通常都内置了一套 try ... except ... finally ...
的错误处理机制,Python 也不例外。
什么是错误?
错误
是指由于逻辑或算法错误等,导致一个程序已无法正常执行的 问题。
什么是异常?
异常
是程序出错时标识的一种 状态,当异常发生时,程序不会再向下执行,而从调用栈向上抛出,转去调用此函数的地方,待相应的错误处理后便恢复为正常状态。
2)错误处理语法
错误处理的关键字顺序如下:
try
/ except
/ else
/ finally
语句
错误处理的语法如下:
try:
可能触发异常的语句
except 错误类型1 [as 变量1]:
异常处理语句1
except 错误类型2 [as 变量2]:
异常处理语句2
except (错误类型3, 错误类型4, …) [as 变量3]:
异常处理语句3
…
except:
异常处理语句other
else:
未发生异常语句
finally:
最终语句
语法说明:
except
子句:可以有一个或多个,但至少要有一个。as
子句 :是用于绑定错误对象的变量,可以省略。else
子句:最多只能有一个,也可以省略。finally
子句:最多只能有一个,也可以省略不写。
处理说明:
- except 子句用来捕获和处理当某种类型的错误发生时,处理异常。
- except 子句会根据错误的类型进行匹配,如匹配成功则调用异常处理语句进行处理,然后程序转为正常状态。
- 如果except子句没有匹配到任何类型的异常则转到except 子句。
- 如果没有任何except子句进行处理,则程序的异常状态会继续下去,并向上层传递。
- 如果没有异常,则执行else 子句中的语句。
- 最后执行finally子句中的语句。
3)异常类的继承层级
Python 中异常类的继承层级如下:
BaseException[所有内建异常类的基类]
+--SystemExit[sys.exit()函数引发的异常,异常不捕获处理,就直接交给python解释器,解释器退出]
+--KeyboardInterrupt[对应的捕获用户中断行为Ctrl+c]
+--GeneratorExit
+--Exception[是所有内建的,非系统退出的异常的基类,自定义异常应该继承自它]
+--自定义异常[从Exception继承的类]
+--RuntimeError
+RecursionError
+--MemoryError
+--NameError[异常类(错误的类型)]
+--StopIteration
+--StopAsyncIteration
+--ArithmeticError[所有算术计算引发的异常,其子类的除零异常等]
+--FloatingPointError
+--OverflowError
+--ZeroDivisionError
+--LookupError[使用映射的键或序列的索引无效时引发的异常的基类:IndexError,KeyError]
+--IndexError
+--KeyError
+--SyntaxError[语法错误:Python将这种错误也归到异常类下面的Exception下的子类,但是这种错误是不可捕获的]
+--OSError
+--BlockingIOError
+--ChildProcessError
+--ConnectionError
+--BrokenPipeError
+--ConnectionAbortedError
+--ConnectionRefusedError
+--ConnectionResetError
+--FileExistsError
+--FileNotFoundError
+--InterruptedError
+--InterruptedError
+--IsADirectoryError
+--NotADirectoryError
+--PermissionError
+--PermissionError
+--ProcessLookupError
+--TimeoutError
二、调试
1)调试介绍
程序能一次写完并正常运行的概率很小,基本不超过 1%。总会有各种各样的 Bug 需要修改。有的 Bug 很简单,看看错误信息就知道,有的 Bug 则比较负责,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的。因此,需要一整套调试程序的手段来修复 Bug。
第一种方法简单有效,就是用 print()
把可能有问题的变量打印出来看看,如下所示:
def foo(s):
n = int(s)
print('>>> n = %d' % n)
return 10 / n
def main():
foo('0')
main()
保存为 err.py
文件后执行,可以在输出中查看打印的变量值,如下所示:
C:\Users\lenovo>python err.py
>>> n = 0
Traceback (most recent call last):
File "C:\Users\lenovo\err.py", line 9, in <module>
main()
File "C:\Users\lenovo\err.py", line 7, in main
foo('0')
File "C:\Users\lenovo\err.py", line 4, in foo
return 10 / n
ZeroDivisionError: division by zero
用 print()
最大的坏处就是将来还得删掉它,想想程序里导出都是 print()
,运行结果也会包含很多垃圾信息。所以,我们又有第二种方法。
2)assert 断言
只要是用 print()
来辅助查看的地方,都可以用 断言(assert)
来替代。如下所示:
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
main()
assert
的意思是,表达式n != 0
应该是True
,否则,根据程序运行的逻辑,后面的代码肯定会出错。
如果断言失败,assert
语句本身就会抛出 AssertionError
。如下所示:
C:\Users\lenovo>python err.py
Traceback (most recent call last):
File "C:\Users\lenovo\err.py", line 9, in <module>
main()
File "C:\Users\lenovo\err.py", line 7, in main
foo('0')
File "C:\Users\lenovo\err.py", line 3, in foo
assert n != 0, 'n is zero!'
AssertionError: n is zero!
实际上,程序中如果到处充斥着 assert
,和 print()
相比也好不到哪去。不过,启动 Python 解释器时可以用 -O
(大写字母o)参数来关闭 assert
。如下所示:
C:\Users\lenovo>python -O err.py
Traceback (most recent call last):
File "C:\Users\lenovo\err.py", line 9, in <module>
main()
File "C:\Users\lenovo\err.py", line 7, in main
foo('0')
File "C:\Users\lenovo\err.py", line 4, in foo
return 10 / n
ZeroDivisionError: division by zero
- 可以看到,报错又从
AssertionError
变成ZeroDivisionError
了。
关闭 assert 后,你可以把所有的 assert 语句当成 pass 来看。
3)logging 日志
把 print()
替换为 logging
是第3种方式,和 assert
相比,logging
不会抛出错误,而且可以输出到文件。如下所示:
import logging
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
执行结果如下:
C:\Users\lenovo>python err.py
Traceback (most recent call last):
File "C:\Users\lenovo\err.py", line 6, in <module>
print(10 / n)
ZeroDivisionError: division by zero
logging.info()
就可以输出一段文本。但是,运行后发现除了ZeroDivisionError
,并没有任何信息,怎么回事?
别急,在 import logging
之后添加一行配置再试试:
import logging
logging.basicConfig(level=logging.INFO)
看到输出了:
C:\Users\lenovo>python err.py
INFO:root:n = 0
Traceback (most recent call last):
File "C:\Users\lenovo\e