目录
背景
第一部分 内置异常
第二部分 异常处理
第三部分 产生异常
第四部分 自定义异常
第五部分 traceback模块
第六部分 cgitb包
第七部分 最佳实践
第八部分 补遗
参考文献及资料
背景
和宇宙空间和时间的无限相比,人类的历史和现有活动只是这个空间和时间上有限抽样。我们无法穷尽所有的可能性,所以新增的事情(错误或异常)是客观存在的。
Python 中有两种错误:语法错误和异常( syntax errors 和 exceptions )。
语法错误,也被称作解析错误。
异常。即使一条语句或表达式在语法上是正确的,当试图执行它时也可能会引发错误。运行期检测到的错误称为 异常,并且程序不会无条件的崩溃
案例环境:Cpython 3.8.8
第一部分 内置异常
Python中一切皆为对象,异常也是。Python3
在builtins.py
内置模块中定义了64个异常类,类继承关系如下。在Python3
中所有异常类本质都是继承BaseException
基类。
1 | BaseException # 异常基类 |
第二部分 异常处理:try-except-else-finally
2.1 语法
大部分语言中都有对异常处理的语法,例如Java中使用 try…catch 语句来捕获代码中的异常,而在Python中使用try…except。原理就是通过监视try语句中的错误,从而让except语句捕获异常信息并进行处理。基本语法结构为:
1 | try: |
- try:正常情况下,程序计划执行的语句。
- except:程序异常是执行的语句。注意这里的exception name可以是多个。
- else:程序无异常即try段代码正常执行后会执行该语句。
- finally:不管有没有异常,都会执行的语句。
另外exception name是异常名。
except
语句不是必须的,finally
语句也不是必须的,但是二者必须要有一个,否则就没有try
的意义了。except
语句可以有多个,Python会按except
语句的顺序依次匹配你指定的异常,如果异常已经处理就不会再进入后面的except
语句。except
语句可以以元组形式同时指定多个异常,参见实例代码。except
语句后面如果不指定异常类型,则默认捕获所有异常,你可以通过logging或者sys模块获取当前异常。
2.2 案例
1 | f = open("test.txt", "r") |
- 文件以read模式打开,显然是无法写入的,except捕获IOError异常回显:not writable。但是程序能正常执行完成(exit code 0)
- 如果我们使用write模式打开,就能顺利执行完
- 另外如果文件不存在,程序不能正常执行完成( exit code 1,非正常退出),中断显示异常:FileNotFoundError。注意这个报错其实是语句
f = open("test.txt", "r")
的报错。
另外语法中except可以是多个分别定义,Python编译器会顺序处理捕获的异常。
第三部分 产生异常:raise和assert
3.1 raise
在调用Python第三方包或者内置包的时候,即使我们没有去捕获异常,通常遇到异常也会捕获。这就是因为第三方包开发者做好了严谨的异常生产和捕获。例如下面的例子我们直接使用raise方法产生一个IOError类型异常,然后被except捕获并处理。语法:
1 | raise [exceptionName [(reason)]] |
等同于C#和Java中的throw
1 | import sys |
1 | >> raise |
3.2 assert 断言
Python中assert用于判断一个表达式(expression),在表达式条件为 false 的时候触发异常。
语法如下:
1 | assert expression |
也等价于:
1 | if not expression: |
看一个例子:
1 | >> assert 1/0 |
第四部分 自定义异常
案例(requests)
除了系统内置的异常类,用户还可以自己定义应用相关的异常类。例如requests包在requests.exceptions中定义自身异常类如下:
1 | IOError |
[‘BaseHTTPError’, ‘ChunkedEncodingError’, ‘CompatJSONDecodeError’, ‘ConnectTimeout’, ‘ConnectionError’, ‘ContentDecodingError’, ‘FileModeWarning’, ‘HTTPError’, ‘InvalidHeader’, ‘InvalidJSONError’, ‘InvalidProxyURL’, ‘InvalidSchema’, ‘InvalidURL’, ‘JSONDecodeError’, ‘MissingSchema’, ‘ProxyError’, ‘ReadTimeout’, ‘RequestException’, ‘RequestsDependencyWarning’, ‘RequestsWarning’, ‘RetryError’, ‘SSLError’, ‘StreamConsumedError’, ‘Timeout’, ‘TooManyRedirects’, ‘URLRequired’, ‘UnrewindableBodyError’, ‘builtins‘, ‘cached‘, ‘doc‘, ‘file‘, ‘loader‘, ‘name‘, ‘package‘, ‘spec‘]
如何定义一个异常类
此外,你也可以通过创建一个新的异常类拥有自己的异常,异常应该是通过直接或间接的方式继承(直接或者间接)自Exception类。
1 | class SelfExceptionError(Exception): |
- 自定义类内部实现了
__init__()
方法和__str__()
方法 - 由于大多数 Python 内置异常的名字都以 “Error” 结尾,所以实际命名时尽量跟标准的异常命名一样
第五部分 traceback模块查看异常
https://www.jianshu.com/p/a8cb5375171a
5.1 案例
在程序出现异常时候,如果只是打印异常(日志文件或回显),通常信息比较少,例如前面写文件的例子,只是返回:not writable。为了快速定位问题,需要知道是哪个文件、哪个模块以及哪一行出错。
Python程序的traceback信息均来源于一个叫做traceback object的对象,而这个traceback object通过函数sys.exc_info()来获取。我们先看下面的例子:
1 | import sys |
程序执行正常退出,回显如下。
1 | not writable |
从回显信息中,有执行模块example4.py
和异常发生的代码行信息:line 7
。通过以上示例可以看出,sys.exc_info()获取了当前处理的exception的相关信息,并返回一个元组(对象名自定义)。
- exc_type,异常的类型(示例是NameError类型);
- exc_value, 异常的value值;
- exc_traceback,traceback object;
5.2 Traceback 模块
模块提供了一个标准接口,用于提取,格式和打印Python程序的堆栈痕迹。 它完全模仿了Python解释器在打印堆栈跟踪时的行为。 当您想在程序控制下打印堆栈迹线时,这非常有用,例如在解释器周围的“包装器”中。
Python中的traceback信息均来源于一个叫做traceback object的对象,而这个traceback object通常是通过函数sys.exc_info()来获取的。
1 | import traceback |
程序运行:
1 | not writable |
sys.exc_info()获取了当前处理的exception的相关信息,并返回一个元组,元组的第一个数据是异常的类型,第二个返回值是异常的value值,第三个就是我们要的traceback objec
5.3 Traceback中常用函数
第六部分 cgitb包
cgitb
模块为Python
脚本提供了一个特殊的异常管理器。名字有点误导人,它最初设计是为了以HTML格式展示cgi
脚本的大量异常信息。后来,他扩展为也可以展示纯文本信息。该模块激活后,如果发生了未捕获的异常,将会展示格式化的输出报告。该报告包括源代码每一层的回溯,以及当前执行程序的参数和局部变量。以及,你可以选择将这些信息存到一个文件里,而不是发送到浏览器。
6.1 案例
1 | import cgitb |
6.2 内部方法和函数
两个函数:
1 | cgitb.encable(display=1, logdir=None, context=5, format="html") |
display
1,发送至浏览器;0, 不发送logdir
如果有的话,写到该目录下context
显示错误代码周围的代码行数format
是否显示为HTML,除了’html’之外的所有值,都会显示为纯文本
1 | cgitb.handle(info=None) |
如果你想用
cgitb
处理异常,你可以调用这个函数。info
应当是含有异常类型、异常值和traceback对象的三元组,——如同sys.exc_info()
返回的那样。如果不提供info,则从sys.exc_info
中获取。
第七部分 最佳实践
- 只处理你知道的异常,避免捕获所有异常然后吞掉它们。
- 抛出的异常应该说明原因,有时候你知道异常类型也猜不出所以然。
- 避免在
catch
语句块中干一些没意义的事情,捕获异常也是需要成本的。 - 不要使用异常来控制流程,那样你的程序会无比难懂和难维护。
- 如果有需要,切记使用
finally
来释放资源。 如果有需要,请不要忘记在处理异常后做清理工作或者回滚操作。
不建议捕获并抛出同一个异常,请考虑重构你的代码。
- 不建议在不清楚逻辑的情况下捕获所有异常,有可能你隐藏了很严重的问题。
- 尽量使用内置的异常处理语句来替换
try/except
语句,比如with
语句,getattr()
方法。
第八部分 补遗
8.1 except Exception语法
在查看Python源码的时候经常看到下面2种写法。
1 | try: |
在语法上,Python2可以使用2种语法均可。但是Python3中只能使用第一种,即:except NameError as e
。为了统一,建议使用第一种写法。
参考文献及资料
1、Understanding the Python Traceback:https://realpython.com/python-traceback/