异常处理

基本语法

异常(错误) 的捕获、处理,主要的语法是 try、except、finally,参考代码如下:

try:
    1/0
except ZeroDivisionError:
    print('ZeroDivisionError')
except Exception as e: # 获得具体的错误作为变量,e
    print(type(e), e)
finally:
    print('done')

代码中,我们先用 1/0 来主动触发错误,然后用 except 来捕获,except 可以多个连续使用,按照次序先捕获到的处理了,后续的 except 就不运行了。
finally 可以有,也可以没有。它的意思: try 的代码块,成功运行了,finnally 内的会执行;如果 try 的失败了,被 except 捕获了,最后,finnaly 的也会执行。

另外,可以用 pass 这样空代码,来忽略所有的异常。

try:
    1/0
except:
    pass

处理异常的理念

有人认为有异常要抛出来,可以发现程序本身的问题;有人认为要尽量处理掉,才能保证程序稳健。
关于如何处理异常的 理念,倒不重要,公说公有理,婆说婆有理,实践过程中,自己权衡。

异常 虽然等同于 错误,但叫做 异常 会确切一些。错误,我们应该是要想办法避免的,异常 则未必,它是一种 显著特征。当 try 失败之后,就可以有一个后备的逻辑在某个 except 中对应。
当然,另外一方面,异常 就是 错误错误 就是 Bug
所以,一个 Exception 作为 异常 还是 错误,具体情况具体分析,没有完全定论,由你自己视情况而定。

构建自己的异常

主动触发 异常,是 raise Exception 这个语法。如果异常主动触发,意味着两点: 1. 当前程序直接跳出到对应的异常处理中 (跟循环中的 break 有点异曲同工的感觉);2,针对不同的异常类型,或许需要进入另外的代码逻辑对应。
如果我们从头构建一个产品,有些时候需要自定义一些异常,按需进行触发,以方便不同流程间快速的切换,甚至可以作为另一种层面的 if-else。参考代码如下:

>>> class MyException(Exception):
...     pass
...
>>> raise MyException
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
__main__.MyException

异常的捕获

一般我们在开发的过程中,利用 PyCharm 进行 Debug 就可以了,即使没有添加过 断点,异常如果抛出,PyCharm 中也会很容易发现异常发生的位置,然后添加断点,后续再复现查看就可以了。
产品正式上线了,直接 Debug 就不大可能,这个时候,一般会使用 https://sentry.io/,它本身是开源的 https://github.com/getsentry/sentry,可以独立部署到自己的 VPS 上。

下面,我们看一下如何呈现 (print) 遇到的 Exception,参考代码:

import sys
def capture_error():
    error_info = sys.exc_info()
    if not error_info:
        return
    e_type, value, tb = error_info[:3]
    try: traceback.print_exception(e_type, value, tb)
    except: pass

如果想把错误的日志,写入到某个日志文件,参考代码:

import sys
error_filepath = 'error.log'
def capture_error_into_file():
    error_info = sys.exc_info()
    if not error_info:
        return
    e_type, value, tb = error_info[:3]
    try:
        with open(error_filepath, 'a') as f:
            traceback.print_exception(e_type, value, tb, file=f)
    except:
        pass

在上面代码中,open(error_filepath, 'a') 这里用了 a 模式打开一个文件,a模式的意思是 append,也就是: 如果文件不存在,创建一个;如果已经存在了,就在末尾添加新内容。

如果我们写了一个客户端的程序,用户使用过程中出现了错误,甚至导致了闪退,这个时候,可以使用上文提到的 sentry 记录错误。另外,也可以直接将错误日志写入到本地的日志文件中。
每一个地方都 try、except,一来很麻烦,二来代码看起来也会比较难看。有没有一个全局的捕获错误的方式?Python 默认全局提供了 hook 机制。但处理这个 hook 要特别注意了,别在处理错误的时候,又触发了新的错误……
参考代码如下:

import sys
sys.excepthook = capture_error_into_file
# exceptbook 需要赋值一个函数,用于处理系统全局捕获到的异常