1 引言

  在我们调试程序时,经常不可避免地出现意料之外的情况,导致程序不得不停止运行,然后提示大堆提示信息,大多是这种情况都是由异常引起的。异常的出现一方面是因为写代码时粗心导致的语法错误,这种错误在程序编译时就可以发现;另一方面也可能是因为程序逻辑错误,这种错误往往是不可避免地,只能通过异常处理来防止程序退出。

2 异常类型

  Python自带的异常处理机制非常强大,提供了很多内置异常类,可向用户准确反馈出错信息。Python是面向对象语言,认为一切皆对象,所以异常也是对象。Python异常处理机制中的BaseException是所有内置异常的基类,但用户定义的类并不直接继承BaseException,所有的异常类都是从Exception继承,且都在exceptions模块中定义。Python自动将所有异常名称放在内建命名空间中,所以程序不必导入exceptions模块即可使用异常。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

  Python内置异常类继承层次结构如下:

BaseException  # 所有异常的基类

 +-- SystemExit  # 解释器请求退出

 +-- KeyboardInterrupt  # 用户中断执行(通常是输入^C)

 +-- GeneratorExit  # 生成器(generator)发生异常来通知退出

 +-- Exception  # 常规异常的基类

      +-- StopIteration  # 迭代器没有更多的值

      +-- StopAsyncIteration  # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代

      +-- ArithmeticError  # 各种算术错误引发的内置异常的基类

      |    +-- FloatingPointError  # 浮点计算错误

      |    +-- OverflowError  # 数值运算结果太大无法表示

      |    +-- ZeroDivisionError  # 除(或取模)零 (所有数据类型)

      +-- AssertionError  # 当assert语句失败时引发

      +-- AttributeError  # 属性引用或赋值失败

      +-- BufferError  # 无法执行与缓冲区相关的操作时引发

      +-- EOFError  # 当input()函数在没有读取任何数据的情况下达到文件结束条件(EOF)时引发

      +-- ImportError  # 导入模块/对象失败

      |    +-- ModuleNotFoundError  # 无法找到模块或在在sys.modules中找到None

      +-- LookupError  # 映射或序列上使用的键或索引无效时引发的异常的基类

      |    +-- IndexError  # 序列中没有此索引(index)

      |    +-- KeyError  # 映射中没有这个键

      +-- MemoryError  # 内存溢出错误(对于Python 解释器不是致命的)

      +-- NameError  # 未声明/初始化对象 (没有属性)

      |    +-- UnboundLocalError  # 访问未初始化的本地变量

      +-- OSError  # 操作系统错误,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合并到OSError中,构造函数可能返回子类

      |    +-- BlockingIOError  # 操作将阻塞对象(e.g. socket)设置为非阻塞操作

      |    +-- ChildProcessError  # 在子进程上的操作失败

      |    +-- ConnectionError  # 与连接相关的异常的基类

      |    |    +-- BrokenPipeError  # 另一端关闭时尝试写入管道或试图在已关闭写入的套接字上写入

      |    |    +-- ConnectionAbortedError  # 连接尝试被对等方中止

      |    |    +-- ConnectionRefusedError  # 连接尝试被对等方拒绝

      |    |    +-- ConnectionResetError    # 连接由对等方重置

      |    +-- FileExistsError  # 创建已存在的文件或目录

      |    +-- FileNotFoundError  # 请求不存在的文件或目录

      |    +-- InterruptedError  # 系统调用被输入信号中断

      |    +-- IsADirectoryError  # 在目录上请求文件操作(例如 os.remove())

      |    +-- NotADirectoryError  # 在不是目录的事物上请求目录操作(例如 os.listdir())

      |    +-- PermissionError  # 尝试在没有足够访问权限的情况下运行操作

      |    +-- ProcessLookupError  # 给定进程不存在

      |    +-- TimeoutError  # 系统函数在系统级别超时

      +-- ReferenceError  # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象

      +-- RuntimeError  # 在检测到不属于任何其他类别的错误时触发

      |    +-- NotImplementedError  # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍然需要添加实际实现

      |    +-- RecursionError  # 解释器检测到超出最大递归深度

      +-- SyntaxError  # Python 语法错误

      |    +-- IndentationError  # 缩进错误

      |         +-- TabError  # Tab和空格混用

      +-- SystemError  # 解释器发现内部错误

      +-- TypeError  # 操作或函数应用于不适当类型的对象

      +-- ValueError  # 操作或函数接收到具有正确类型但值不合适的参数

      |    +-- UnicodeError  # 发生与Unicode相关的编码或解码错误

      |         +-- UnicodeDecodeError  # Unicode解码错误

      |         +-- UnicodeEncodeError  # Unicode编码错误

      |         +-- UnicodeTranslateError  # Unicode转码错误

      +-- Warning  # 警告的基类

           +-- DeprecationWarning  # 有关已弃用功能的警告的基类

           +-- PendingDeprecationWarning  # 有关不推荐使用功能的警告的基类

           +-- RuntimeWarning  # 有关可疑的运行时行为的警告的基类

           +-- SyntaxWarning  # 关于可疑语法警告的基类

           +-- UserWarning  # 用户代码生成警告的基类

           +-- FutureWarning  # 有关已弃用功能的警告的基类

           +-- ImportWarning  # 关于模块导入时可能出错的警告的基类

           +-- UnicodeWarning  # 与Unicode相关的警告的基类

           +-- BytesWarning  # 与bytes和bytearray相关的警告的基类

           +-- ResourceWarning  # 与资源使用相关的警告的基类。被默认警告过滤器忽略。

3 异常捕获与处理

  当发生异常时,我们就需要对异常进行捕获,然后进行相应的处理。使用Python异常处理机制时,把可能发生错误的语句放在try模块里,用except来处理异常,每一个try,都必须至少对应一个except。Python异常处理机制常用的几种异常捕获和处理结构如下:

  第一种:try - except

try:

    <语句>

except:

    <异常处理>

  这种结构使用简单,但可能会引发一些设计问题:尽管使用方便,但可能捕获与程序无关、意料之外的系统异常,而且可能意外拦截其他处理器的异常。例如,在Python中,即表示系统离开调用(sys.exit())也会出发异常,然而这种异常我们通常不需要捕获。所以,这种结构尽量少用。

import time

import sys

try:

    while True:

        a = int(input('请输入一个数字:'))

        if a==0:

            sys.exit()

        else:

            print('您输入的数字是:{}'.format(a))

        time.sleep(1)

except:

    print('发生异常了……')

  当输入数字0时,输出如下:

  请输入一个数字:0

  发生异常了……

  事实上,系统知识正常退出,并不算异常,但是只使用except,Python会将系统离开调用当做异常来捕获。

  (2)try-except<异常名>

try:

    <语句>

 except <异常名> [as e]:

    <异常处理>

  except中,as e是可选的,意思是将捕获的异常类实例化对象赋值给e(当然也可以用其他变量名),在except下面的代码块中,我们将可以通过这个e访问异常实例化对象中的方法和数据。另外,except子句的个数理论上是不限的,不过不能将父类置于子类前面。在上文中提到,Exception类是所有Python异常类的父类,所以except Exception将可以捕获任何异常,换句话说,它是万能异常处理句式。

try:

    a = int(input('请输入一个数字:'))

except ValueError as e:

    print(e)

  当输入一个非数字类字符时,输出如下:

  请输入a的值:j

  invalid literal for int() with base 10: 'j'

  如果输入b的值为0,输出如下:

  请输入a的值:1

  请输入b的值:0

  division by zero

  (3)try-except (<异常类1>, <异常类2>, ...)

try:

    <语句>

except (<异常类1>, <异常类2>, ...):

    <异常处理>
try:

    a = int(input('请输入a的值:'))

    b = int(input('请输入b的值:'))

    c = a/b

except (ValueError , ZeroDivisionError) as e:

    print(e)

  当输入一个非数字类字符时,输出如下:

  请输入a的值:j

  invalid literal for int() with base 10: 'j'

  如果输入b的值为0,输出如下:

  请输入a的值:1

  请输入b的值:0

  division by zero

   (4)try-except-else

try:

    <语句>

except <异常名>:

    <异常处理>

else:

    <语句>  # try语句中没有异常则执行此段代码

  如果说except是在try中代码抛出异常时执行,那么else语句下面的代码将在try顺利执行(没有抛出任何异常)的情况下才会执行。

try:

    a = int(input('请输入a的值:'))

    b = int(input('请输入b的值:'))

    c = a/b

except (ValueError , ZeroDivisionError) as e:

    print(e)

else:

    print('a/b的结果为:{}'.format(c))

  当输入a和b的值都为数字时,才会执行else部分代码,输出结果如下:

  请输入a的值:4

  请输入b的值:2

  a/b的结果为:2.0

  (5)try-except-finally

try:

    <语句>

except <异常类>:

    <异常处理>

finally:

    <语句>  # 不管try中代码是否抛出异常,都会执行此段代码

  finally中的代码无论try中代码是否抛出异常都会执行。

try:

    a = int(input('请输入a的值:'))

    b = int(input('请输入b的值:'))

    c = a/b

except (ValueError , ZeroDivisionError) as e:

    print(e)

else:

    print('a/b的结果为:{}'.format(c))

finally:

    print('无论你输入什么值,finally都会执行……')

4 主动抛出异常(raise)

  有时候,异常可以作为代码运行的标志,通过主动触发异常可以改变代码的运行路线,从而提高代码健壮性。主动触发异常需使用raise关键字,其语法结构如下:

  raise [Exception [, args [, traceback]]]
def fun(x , y):

    try:

        print('fun()方法开始执行……')

        if isinstance(x,int) and isinstance(y,int):

            return x+y

        else:

            raise TypeError('类型错误')

    except Exception as e:

        print(e)

    finally:

        print('fun()方法执行结束……')

fun(2 , '3')

  输出结果:

  fun()方法开始执行……

  类型错误

  fun()方法执行结束……

5 断言(assert)

  assert语句根据后面的表达式的真假来控制程序流。 asset语法结构如下:

assert expression,'information'

       若为expression结果为True,则往下执行。若为False,则中断程序并调用默认的异常处理器抛出AssertionError异常,同时输出指定的提示信息。

def fun(x):
    print('fun()方法开始执行……')
    assert x<0 , '抛出异常,x小于0'
    print('fun()方法执行结束……')
try:
    fun(2)
except Exception as e:
    print(e)

  输出结果:

  fun()方法开始执行……
  抛出异常,x小于0

  可以发现,打印输出第一行语句之后,由于断言失败,抛出异常,程序直接退出。

6 with/as上下文管理器

  with/as语句通常是作为try/finally语句的替代方案,不过with/as更加优雅。在有一些任务中,可能事先需要设置,然后不管在任务过程中是否顺利(有无异常抛出),最后后做清理工作。对于这种场景, with/as语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读写数据,但不管读写数据是否有异常发生,最后都要关闭文件句柄。

  with/as语句的基本格式如下:

with expression [as variable] :

    with-block

  在这里的expression会返回一个对象,as子句是可选的,当存在as子句时,expression返回的对象会赋值给variable。

  使用with/as语句将一段字符串写入文件:

with open('data.txt' , 'w') as myfile:

    myfile.write('123456789')

         不适用with/as语句,如果要实现同样的效果,只能这么写:

try:

    myfile = open('data.txt' , 'w')

    myfile.write('123456789')

except Exception as e:

    print(e)

finally:

    myfile.close()

7 自定义异常类

         如果Python提供的内置异常内不满足使用要求,那么,可以自定义一个异常类。自定义异常类必须继承Exception类,并且在使用时,必须通过raise关键字自动触发。

class MyError(Exception):

    def __init__(self, info):

        self.info = info

    def __str__(self):

        return '{}:{}'.format(self.__class__ .__name__, self.info)

try:

    raise MyError('自定义的异常……')

except MyError as e :

print(e)

  输出结果:

  MyError:自定义的异常……

8 总结

  本文是对Python异常处理机制的总结,较为全面的介绍了Python异常处理的常用内置类,即几种异常捕获/处理句式结构,主动触发异常,断言,with上下文管理协议,自定义异常类等内容。

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄