Python回顾与整理8:错误和异常

发布时间:2019-09-14 09:27:22编辑:auto阅读(1531)

    0.说明


            如果想写出用户体验高的代码,那么就需要考虑到在执行自己写的这段代码中在和用户交互的过程中可能会出现的问题,也就是说,需要对可能出现的异常进行处理,只有做好这些工作,才能写出用户体验好的代码。




    1.什么是异常


    • 错误

            错误是语法(导致解释器无法解释)或逻辑(也就是代码质量问题)上的,在Python中,当检测到错误时,解释器会指出当前流无法继续执行下去,于是就出现了异常。

    • 异常

            程序出现了错误而在正常控制流以外采取的行为。

            根据上面的解释,可以理解为,只要解释器检测到程序运行时出现了错误(与Python解释器不相容而导致),就会触发一个异常。




    2.Python中的异常


            如下:

    异常类型描述简单例子
    NameError尝试访问一个未声明的变量,或者是在名称空间中不存在的变量
    >>> xpleaf
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'xpleaf' is not defined
    ZeroDivisionError除数为零
    >>> 1/0
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ZeroDivisionError: integer division
     or modulo by zero
    SyntaxError

    Python解释器语法错误

    (唯一不是在运行时发生的异常,发生在编译时,Python解释器无法把相关脚本编译为Python字节代码)

    >>> for
      File "<stdin>", line 1
        for
          ^
    SyntaxError: invalid syntax
    IndexError请求的索引走出序列范围
    >>> aList = []
    >>> aList[0]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    IndexError: list index out of range
    KeyError请求一个不存在的字典关键字
    >>> aDict = {'name': 'xpleaf', 'love': 'cl'}
    >>> aDict['clyyh']
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'clyyh'
    IOError

    输入/输出错误

    (任何类型的I/O错误都会引发IOError异常)

    >>> f = open('xpleaf')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    IOError: [Errno 2] No such file or 
    directory: 'xpleaf'
    AttributeError尝试访问未知的对象属性
    >>> class myClass(object):
    ...   pass
    ... 
    >>> myInst = myClass()
    >>> myInst.name
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'myClass' object has no 
    attribute 'name'




    3.检测和处理异常


            需要注意的是,这和前面提到的检测和处理错误并不一样,检测和处理错误的结果是会引发一个异常,这是由Python解释器完成的;当然我们也可以人为地触发一个异常,这时开发者会认为,用户对程序的使用是不正确的,所以才引发这样一个异常。

            当异常出现的时候,如果不对该异常进行处理,那么Python解释器就会中止当前程序的运行,因此,我们需要对异常进行处理,以达到即使异常出现了,也不会中止程序的执行。


    (1)try-except语句

    • 语法

    try:
        try_suite    #监测这里的异常
    except Exception[, reason]:
        except_suit    #异常处理代码

            reason是错误原因,由捕获的异常本身带有,只需要定义一个变量即可以对其进行使用。

            打开一个不存在的文件时:

    >>> f = open('xpleaf')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    IOError: [Errno 2] No such file or directory: 'xpleaf'

            其中:

    [Errno 2] No such file or directory: 'xpleaf'

            便是错误原因,可以使用try-except语句来处理上面的异常:

    >>> try:
    ...   f = open('xpleaf', 'r')
    ... except IOError, e:
    ...   print 'could not open file:', e
    ... 
    could not open file: [Errno 2] No such file or directory: 'xpleaf'

    忽略代码,继续执行,向上移交:

    指的是,如果该层代码(比如一个函数内)有相关的异常处理器(即except语句),就会跳到该异常处理器中进行处理,后面的代码会被忽略(后面的其它except语句);如果在该层没有找到对应的异常处理器,该异常会被向上移交,比如移交到调用该函数的上层代码;当异常到达最顶层仍然没有找到对应处理器时,就认为这个异常是未处理的,Python解释器会显示出跟踪记录,然后退出。


    (2)带有多个except的try语句

    • 语法

    try:
        try_suite
    except Exception1[, reason1]:
        suite_for_exception_Exception1
    except Exception2[, reason2]:
        suite_for_exception_Exception2

            需要注意的是,当有异常发生时,一旦找到对应的异常处理器,程序的执行流就会跳转到该异常处理器中,其它的except语句将会被忽略。


    (3)处理多个异常的except语句

    • 语法

    try:
        try_suite
    except (Exception1, Exception2)[, reason1]:
        suite_for_exception_Exception1_and_Exception2

            需要注意的是,这些不同的异常应该被放入到一个元组中。

    拓展:包装内建函数

    如下:

    >>> def safe_float(obj):
    ...   try:
    ...     retval = float(obj)
    ...   except (ValueError, TypeError):
    ...     retval = 'argument must be a number or numeric string'
    ...   return retval

    执行如下:

    >>> safe_float(123)
    123.0
    >>> safe_float('123')
    123.0
    >>> safe_float('foo')
    'argument must be a number or numeric string'

    这是一种非常不错的技巧,要善于利用。


    (4)捕获所有异常


            如果需要捕获所有因错误而引起的异常,可以直接捕获Exception异常,Exception是绝大多数Python内建异常的基类。

            但是对于SystemExit和KeyboardInterupt这两个异常,使用Exception是无法捕获的,因为它们不是Exception的继承者,原因很简单,因为这两个异常不是由于错误条件引起的。SystemExit是由于当前Python应用程序需要退出,KeyboardInterrupt代表用户按下了ctrl-c,想要关闭Python。

            但是这三者都有一个共同的基类,那就是BaseException,也就是这三者在程序结构上是同级的,如下:

    BaseException
      -KeyboardInterrupt
      -SystemExit
      -Exception
        -(all other current built-in exceptions)

            因此,如果真的想要捕获所有的异常(包括非错误条件引起的),就可以使用BaseException,可以看下面的例子:

    • 使用Exception:无法捕获KeyboardInterrupt

            代码如下:

    try:
        name = raw_input('Your name:')
    except Exception:
        print 'quit'

            执行如下:

    /usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/10/test.py
    Your name:Traceback (most recent call last):
      File "/home/xpleaf/PycharmProjects/Python_book/10/test.py", line 3, in <module>
        name = raw_input('Your name:')
    KeyboardInterrupt
    • 使用BaseException:捕获所有异常(错误与非错误条件引起的)

            代码如下:

    try:
        name = raw_input('Your name:')
    except BaseException:
        print 'quit'

            执行如下:

    /usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/10/test.py
    Your name:quit

            这样的好处是,如果需要同时捕获三个同级的异常,使用一个except语句就可以。


            但是需要注意的是,try-except语句是为了更好地跟踪潜在的错误并在代码里准备好处理异常的逻辑,不应该将其作为异常过滤器来捕获所有异常,并忽略掉这些异常。


    (5)异常参数

            其实所谓异常参数,对于前面的一个例子,为什么使用e错误原因时,就可以得到与该异常相关的字符串信息呢?那是因为,异常引发后,它传递了一个参数给异常处理器。

            直接看下面一个例子:

    >>> try:
    ...     float('foo')
    ... except ValueError, e:
    ...     print 'Error Happen:', e
    ... 
    Error Happen: could not convert string to float: foo
    >>> 
    >>> type(e)
    <type 'exceptions.ValueError'>
    >>> str(e)
    'could not convert string to float: foo'
    >>> print e
    could not convert string to float: foo
    >>> e.__class__
    <type 'exceptions.ValueError'>
    >>> e.__class__.__name__
    'ValueError'
    >>> e
    ValueError('could not convert string to float: foo',)

            我们可以得出下面的结论:

    • 异常引发时,如果使用错误原因变量,实际上,这是一个包含来自导致异常的诊断信息的类实例,异常参数自身会组成一个元组,并存储为这个异常类的属性

            在这个例子中的分析是,引发了ValueError异常,然后e就是该异常的一个实例,并且在生成这个实例e的过程中,异常参数('could not convert string to float: foo',)(注意这是一个元组),就会成为e的一个属性,而使用str(e)可以输出诊断信息的字符串,那是因为调用了该类实例的__str__()方法 。

            注意,如果用一个except语句来同时捕获多个异常时,使用一个错误原因即可,因为每一个异常都会生成自己的异常参数。

            再强调:

    • 异常参数是该异常发生时传递给异常处理器的一个字符串对象,它会成为这个异常类的实例的一个属性,并且可以通过调用str()来获得该诊断信息(使用print语句,实际也是调用了该str()方法)

    拓展:继续前面的float()例子

    代码如下:

    def safe_float(object):
        try:
            retval = float(object)
        except (ValueError, TypeError), diag:
            retval = str(diag)
        return retval
    
    result = safe_float('foo')
    print result
    result2 = safe_float([])
    print result2

    执行如下:

    /usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/10/test.py
    could not convert string to float: foo
    float() argument must be a string or a number

            PS:更进一步学习,可以考虑参考异常类的源代码。


    (6)else子句

            没有捕获到异常时,就执行else子句中的代码块,一个简单的例子如下:

    >>> try:
    ...     float(4)
    ... except (ValueError, TypeError), e:
    ...     print 'Error Happen:', e
    ... else:
    ...     print 'No Exceptions'
    ... 
    4.0
    No Exceptions


    (7)finally子句

            即无论异常是否有发生或是否捕捉到异常,都会执行的语句块。

            常用的方式如下:

    • try-except-finally

    try:
        A
    except Exception1, e:
        B
    finally:
        C
    • try-except-else-finally

    try:
        A
    except Exception1, e:
        B
    else:
        C
    finally:
        D

            至于书本上说的各种形式上的问题,则可以不用考虑太多,在实践中使用时加以使用把可能出现的情况考虑到就可以了。




    4.上下文管理


            try-except和try-finally的一种特定的用法是保证共享的资源的唯一分配,并在任务结束的时候释放它,比如文件、线程资源、简单同步、数据库连接等,以打开文件为例。但其实如果用with语句,会方便很多:

    >>> with open('xpleaf.txt', 'r') as f:
    ...     for eachLine in f:
    ...         print eachLine
    ... 
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    IOError: [Errno 2] No such file or directory: 'xpleaf.txt'

            with语句帮我们做了很多事情:试图打开一个文件,如果一切正常,把文件对象赋值给f.然后用迭代器遍历文件中的每一行,当完成时,关闭文件,无论在这一段代码的开始、中间还是结束时发生异常,会执行清理的代码,此外文件仍会被自动的关闭。

            当然这种方法仅适用于支持上下文管理协议的对象。关于上下文管理协议,由于目前还没有使用到,所以暂不做总结。




    5.字符串作为异常


            知道有这种情况就可以,在实际中仍然使用类异常。




    6.触发异常


            使用raise关键字就可以人为地触发各种异常。

    • 语法

    raise [SomeException [, args [, traceback]]]

            其用法可以有如下:

    raise语句的用法
    raise语法描述
    raise exclass触发一个异常,从cxclass生成一个实例(不含任何异常参数)
    raise exclass()同上,但现在不是类;通过函数调用操作符(其实就是指加上了`()`)作用于类生成一个新的exclass实例,同样也没有异常参数
    raise exclass, args同上,但同时提供的异常参数args,可以是一个参数也可以是元组
    raise exclass(args)同上
    raise exclass, args, tb同上,但提供一个跟踪记录(traceback)对象tb供使用
    raise exclass, instance通过实例触发异常(通常是exclass的实例);如果实例是exclass的子类实例,那么这个新异常的类型会是子类的类型(而不是exclass);如果实例既不是exclass的实例也不是exclass子类的实例,那么会复制此实例为异常参数去生成一个新的exclass实例
    raise instance
    通过实例触发异常:异常类型是实例的类型;等价于raise instance.__class__, instance(同上)
    raise重新触发前一个异常,如果之前没有异常,触发TypeError

            对于raise string以及相关的方法,这里就不提及了,因为实际上很少用到,另外对于traceback也不常用。可举例如下:

    • raise exclass

    >>> raise ValueError
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError
    • raise exclass()

    >>> raise ValueError()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError
    • raise exclass, args

    >>> raise ValueError, 'Something wrong happen about value'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: Something wrong happen about value
    >>> 
    >>> raise ValueError, ('New Error', 'Something wrong happen about value')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: ('New Error', 'Something wrong happen about value')
    • raise exclass(args)

    >>> raise ValueError('Something wrong happen about value')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: Something wrong happen about value
    • raise exclass, instance

    >>> newError = ValueError('Something wrong happen about value')
    >>> raise ValueError, newError
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: Something wrong happen about value
    >>> 
    >>> newError = ValueError('Something wrong happen about value')
    >>> raise IOError, newError
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    IOError: Something wrong happen about value
    # 注意看异常类型和异常参数
    • raise instance

    >>> newError = ValueError('Something wrong happen about value')
    >>> raise newError
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: Something wrong happen about value
    >>> 
    >>> raise newError.__class__, newError
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: Something wrong happen about value
    • raise

    >>> raise
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType
    # 即达不到所描述的效果,即使前面已经有异常出现,还是会触发TypeError异常




    7.断言


            断言通过assert语句实现,测试一个表达式,如果返回值是假,触发异常。触发异常时,可以像处理普通异常一样对它进行处理。

    • 语法

    assert expression[, arguments]

            举例如下:

    >>> assert 1 == 1
    >>> assert 1 == 0
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AssertionError
    >>>
    >>> assert 1 == 0, 'One does not equal zero silly!'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AssertionError: One does not equal zero silly!




    8.标准异常


            所有的标准异常都是内建的,所以可以直接在交互器或执行脚本文件时使用,关于Python当前的标准异常集,其实只要查看源代码就可以很清晰地知道有哪些标准异常了,这里就不再列出来了。

            另外,有3个直接从BaseException派生的异常子类:

    • SystemExit

    • KeyboardInterrupt

    • Exception

            其它的所有内建异常都是Exception的子类。




    9.创建异常


            其实创建异常,只需要继承一个异常,并根据自己的需要进行定制即可,但由于目前还使用不到,所以先略过,实际上可以通过书上的例子和异常类的源代码来加深对Python面向对象编程的理解,往后再做整理。




    10.(现在)为什么用异常


            肯定是需要用异常的,因为需要达到这样的目的:运行环境必须足够强健,来处理应用级别的错误,并提供用户级别的错误信息。这样才能提供良好的用户体验。




    11.到底为什么要异常


            没有异常,将会导致很多问题。

            



    12.异常和sys模块


            可以通过sys模块中的exc_info()函数来获取异常信息,举例如下:

    >>> try:
    ...     float('abc123')
    ... except:
    ...     import sys
    ...     exc_tuple = sys.exc_info()
    ... 
    >>> print exc_tuple
    (<type 'exceptions.ValueError'>, ValueError('could not convert string to float: abc123',), <traceback object at 0x7f9ba0fa0f38>)

            可以看到,从sys.exc_info()中得到一个三元组,元素分别如下:

    • exc_type:异常类

    • exc_value:异常类的实例

    • exc_traceback:跟踪记录对象

            跟踪记录对象提供了发生异常的上下文,包含诸如代码的执行帧,异常发生时的行号等信息。




    13.相关模块


            如下:

    异常相关的标准库
    模块描述
    exceptions内建异常(不需要导入这个模块)
    contextlib为使用with语句的上下文对象工具
    sys主要是sys.exc_info()




关键字