Python异常处理的哲学


所谓异常指的是程序的执行出现了非预期行为,就好比现实中的做一件事过程中总会出现一些意外的事。异常的处理是跨越编程语言的,和具体的编程细节相比,程序执行异常的处理更像是哲学。限于认知能力和经验所限,不可能达到像解释器下import this看到的Python设计之禅一样,本文就结合实际使用简单的聊一聊。

0. 前言

工作中,程序员之间一言不合就亮代码,毕竟不管是代码本身还是其执行过程,不会存在二义性,更不会含糊不清,代码可谓是程序员之间的官方语言。但是其处理问题的逻辑或者算法则并非如此。

让我至今记忆犹新的两次程序员论剑有:

反问一:项目后期所有的异常处理都要去掉,不允许上线后出现未知的异常,把你这里的异常处理去掉,换成if else;

反问二:这里为什么要进行异常处理?代码都是你写的,怎么会出现异常呢?

这是我亲身经历的,不知道大家碰到这两个问题会怎样回答,至少我当时竟无言以对。这两个问题分别在不同的时间针对不同的问题出自一个互联网巨头中某个资深QA和资深开发的反问。

暂且不论对错,毕竟不同人考虑问题的出发点是不同的。但是从这么坚决的去异常处理的回答中至少有一点可以肯定,那就是很多人对自己的代码太过自信或者说是察觉代码潜在问题的直觉力不够,更别提正确的处理潜在的问题以保证重要业务逻辑的处理流程。写代码的时候如果只简单考虑正常的情况,那是在往代码中下毒。

接下类本篇博文将按照套路出牌(避免被Ctrl + W),介绍一下python的异常处理的概念和具体操作.

1. 为什么要异常处理

常见的程序bug无非就两大类:

  • 语法错误;
  • 逻辑不严谨或者思维混乱导致的逻辑错误;

显然第二种错误更难被发现,且后果往往更严重。无论哪一种bug,有两种后果等着我们:一、程序崩掉;二、执行结果不符合预期;

对于一些重要关键的执行操作,异常处理可以控制程序在可控的范围执行,当然前提是正确的处理。

比如我们给第三方提供的API或者使用第三方提供的API。多数情况下要正确的处理调用者错误的调用参数和返回异常结果的情况,不然就可能要背黑锅了。

在不可控的环境中运行程序,异常处理是必须的。然而困难的地方是当异常发生时,如何进行处理。

2. python异常处理

下面逐步介绍一下python异常处理相关的概念。

2.1 异常处理结构

必要的结构为try ... except,至少有一个except,else 和 finally 可选。

try:
    code blocks
except (Exception Class1, Exception Class2, ...) as e:
    catch and process exception
except Exception ClassN:
    catch and process exception
... ...
else:
    when nothing unexpected happened
finally:
    always executed when all to end

2.2 python 内置异常类型

模块exceptions中包含了所有内置异常类型,类型的继承关系如下:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |        +-- WindowsError (Windows)
      |    |        +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |        +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |        +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
          +-- DeprecationWarning
          +-- PendingDeprecationWarning
          +-- RuntimeWarning
          +-- SyntaxWarning
          +-- UserWarning
          +-- FutureWarning
      +-- ImportWarning
      +-- UnicodeWarning
      +-- BytesWarning

2.3 except clause

excpet子句的常用的写法如下:

  • except:                         # 默认捕获所有类型的异常
  • except Exception Class:                   # 捕获Exception Class类型的异常
  • except Exception Class as e:                 # 捕获Exception Class类型的异常,异常对象赋值到e
  • except (Exception Class1, Exception Class2, ...) as e:      # 捕获列表中任意一种异常类型

上面的异常类可以是下面python内置异常类型,也可以是自定义的异常类型。

2.4 异常匹配原则

  • 所有except子句按顺序一一匹配,匹配成功则忽略后续的except子句;
  • 若抛出异常对象为except子句中给出的异常类型的对象或给出的异常类型的派生类对象,则匹配成功;
  • 如果所有的except子句均匹配失败,异常会向上传递;
  • 如果依然没有被任何try...except捕获到,程序在终止前会调用sys.excepthook进行处理;

2.5 else & finally

如果没有异常发生,且存在else子句,则执行else子句。只要存在finally子句,无论任何情况下都会被执行。

可能唯一不好理解的地方就是finally。没有异常、捕获异常、异常上传以及异常处理过程中发生异常等均会执行finally语句。

下面看个例子:

def division(a, b):
    try:
        print'res = %s' % (a / b)
    except (ZeroDivisionError, ArithmeticError) as e:
        return str(e)  # 注意此处使用的是return
    else:
        print '%s / %s = %s' % (a, b, a / b)
    finally:
        print 'finally clause'

 分别输入参数(1, 2),(1, 0)和 (1,“0”)执行:

print 'return value: %s' % division(a, b)

得到的结果如下:

res = 0
/ 2 = 0
finally clause
return value: None

finally clause
return value: integer division or modulo by zero

finally clause
Traceback (most recent call last):
  File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 217, in <module>
    print 'return value: %s' % division(1, "0")
  File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 208, in division
    print'res = %s' % (a / b)
TypeError: unsupported operand type(s) for /: 'int' and 'str'

可以看到纵使程序发生异常且没有被正确处理,在程序终止前,finally语句依旧被执行了。可以将此看做程序安全的最后一道有效屏障。主要进行一些善后清理工作,比如资源释放、断开网络连接等。当然with声明可以自动帮我们进行一些清理工作。

2.6 raise抛出异常

程序执行过程中可以使用raise主动的抛出异常. 

https://www.linuxboy.net/topicnews.aspx?tid=17

linuxboy的RSS地址:https://www.linuxboy.net/rssFeed.aspx

本文永久更新链接地址:https://www.linuxboy.net/Linux/2020-02/162396.htm

相关内容