根据WikiPedia的文献,一段代码是异常安全的,如果这段代码运行时的失败不会产生有害后果,如内存泄露、存储数据混淆、或无效的输出。我们可以知道一段代码的异常安全通常分为下面五类:
异常安全通常分为5个层次:
失败透明:如果出现了异常,将不会对外进一步抛出该异常。(一般比较复杂)
强异常安全:可以运行失败,不过数据会回滚到代码运行前(无副作用)
基本异常安全:运行失败导致的数据变更,使得代码运行前后数据不一致了(有副作用)
最小异常安全:运行失败保存了无效数据,但是还不会引起崩溃,资源不会泄露(进程不会挂)
异常不安全:没有任何保证(进程可能会挂掉)
从上述的5个层次来看,我们可以知道,在平时写代码的时候,对数据库、文件、网络等的IO操作都是需要尽量保证无副作用的,也就是强异常安全。具体来说就是,RDBS操作在失败的时候需要回滚机制、所有IO操作在***要保证IO连接资源关闭。
其实和多数语言的异常机制的语法是类似的:Python和R都是通过抛出一个异常对象或一个枚举类的值来返回一个异常;异常处理代码的作用域由try开始,以***个异常处理子句(catch, except等)结束;可连续出现若干个异常处理子句,每个处理特定类型的异常。***通过finally子句,无论是否出现异常它都将执行,用于释放异常处理所需的一些资源。
下面将具体介绍二者的异常处理机制。
Python 中的异常处理机制
首先,Python 是一门面向对象语言,所有的异常类都是通过继承BaseException类来实现的,我们亦可以通过相应的继承来实现自定义的异常类,比如在工作流调度中使用AirflowException,具体实现可以直接看Airflow的源码。
事实上,这些在我们代码处理范围内的异常其实就是可以分成两个部分:
IO异常:由网络抖动、磁盘文件位置变更、数据库连接变更等引起的IO异常问题。
运行期异常:由于计算或者传输的参数参数类型有误、参数值异常等等发生在运行期的异常,都统一被称为运行期异常。正常来说,IO上的异常我们都要有相应的try-catch-finally机制,在Python也就是如下实现:
try:
do something with IO
except:
do something without IO
finally:
close IO
这里容易犯的一个错误就是在except中又引入了新的IO操作,比如在except中又引入了一个API的POST请求或者数据库写操作等等,这样如果在except阶段又发生了异常,将导致异常信息的丢失。
另一方面,对于可能的运行期异常则需要我们根据具体应用场景的需求来做相应的处理,一般就是遇到一个新的问题加一个新的异常捕获机制,当然这里也就考验到码农程序设计的功利,是否能够未雨绸缪。比如数组长度的检查,传入字典的Key检查等等。Python本身提供了丰富的异常处理类型并且易于拓展,正确使用将可以显著提升程序的鲁棒性(保住码农的饭碗)。