TOC

异常处理(Exception)

    异常(Exception),对于异常而言,也就是说程序写的没有问题,但是在某些情况下会导致程序无法正常的执行下去,例如open函数去操作一个文件,但文件不存在,或者已经存在一个同名文件,这就是异常,当异常发生之后,程序也就停止了,需要知道的是,异常是不可避免的,而对于Python的异常处理,如下格式。
try:
    <语句> # 运行代码
except <异常类>:
    <语句> # 捕获到上述指定的异常时运行的代码
except <异常类> as <变量名>: # 将前面的异常类通过异常初始化一个实例并赋值给as后面的变量名
    <语句> # 捕获到上述指定的异常时运行的代码
else:
    <语句> # 如果没有异常发生则执行
finally:
    <语句> # 不论是否有异常都执行

错误和异常

    在编程的时候会经常遇到两类问题,一类是错误,一类的异常,对于错误(Error),比如逻辑错误,某个地方应该是加,但是写成了减,这就是逻辑错误,但是逻辑上出错了,它并不会直接显现出来,只不过可能在结果中可能会带来问题,这种称之为逻辑错误,逻辑错误一般并不能直接表现。
    在高级编程语言中,一般都有错误和异常的概念,异常是可以捕获并被处理的,但是错误是无法捕获的,如下就是错误和异常的表现,可以看出,一个健壮的系统应该尽可能的避免错误,尽可能的捕获、处理异常。
# 错误
def 0A():
    pass
# SyntaxError: invalid syntax 
# ---
# 异常
with open("test",'r') as f:
    pass
# FileNotFoundError: [Errno 2] No such file or directory: 'test'

产生异常

    产生异常的方式很简单,有两种,第一种,解释器自动引发异常,比如上述的打开一个文件,或者1/0时,这种异常都是在运行期跑出来的,它是由解释器发现的,然后解释器会自动引发异常。
    还有一种异常,它是我们手动抛出的,我们用这种方式给它抛出一个异常来,如下实例,如果一个函数的参数不是int类型,则抛出异常。
    程序会在抛出异常的地方中断执行,如果不捕获,程序则种植当前线程的执行。
# 解释器引发异常
print(1 / 0) # ZeroDivisionError: division by zero
# 手动抛出异常
def cce(n):
    if isinstance(n,int):
        pass
    else:
        raise TypeError("输入错误")
cce("cce") # TypeError: 输入错误

异常捕获

    一般情况下,在程序设计时,都会将可预知的异常进行捕获,因为如果不捕获,这个异常就会带来及其严重的后果,因为异常一旦产生之后,当前运行的线程就会崩掉了,这个问题非常棘手,如果异常处理不好,再不做日志处理,程序在运行的过程中产生的问题,也就没人知道它到底是什么原因造成的,往往程序都会有异常,但是如果不做异常捕获,我们连哪里出问题了都不知道,所以说异常捕获是程序设计的必备功能。
    异常捕获可以使用try和except来进行捕获,我们可以将要监测是否会出现异常的代码放在try和except之间,这个try和except之间的代码,如果出现任何异常,将会被except捕获,捕获之后,我们就可以做处理,那么此时,做了异常处理,我们的程序也就不会再因为这个异常而终止,那么即使出现了异常,代码也将继续往下执行,不会出现异常发生导致中断程序执行的现象。
try:
    带捕获异常的代码块
except [捕获的异常类型]:
    异常的处理代码块
    同时,except也可以指定需要捕获的异常类型,如果不指定类型,那么将全部捕获,如果指定了类型将只捕获指定类型,出现其他类型,则程序中断停止,当然如果在try和except之间的代码,没有出现异常,那么except下面的处理代码也将不会执行。
try:
    print(1/0)
except ZeroDivisionError:  # 只捕获除0异常
    print("Error") # Error

抛出异常

    抛出异常,主要使用raise来实现,它主要是用于当程序出现错误,非内置的系统错误,而是程序上的错误,那么此时就可以通过raise显式地将异常手动抛出来。一旦执行了raise语句,raise后面的语句将不能执行。
    同时,需要注意的是,raise必须是继承自BaseException异常基类的子类,当然,我们也可以自定义异常同时继承系统内置的异常类。
raise IndexError
# Traceback (most recent call last):
#   File "/usr/local/Project/cce/Py/edu.py", line 7, in <module>
#     raise IndexError
# IndexError
    raise还有一种特殊用法,就是在except里面,使用raise,则会将捕获到到的异常重新抛出,有这么一种情况,比如,一个程序,在执行时可能会出现异常,程序员就给他加入了try和except,来捕获异常,但是它并不想让这个异常消失,而是只是做一个简单的日志记录。
    那么为了达到这种目的,我们可以在except里面使用raise,直接将当前异常抛出,先捕获,后抛出,如下示例。
# 可以看到下面的结果,虽然我们捕获了ZeroDivisionError,但是如果在except里面使用一个raise,则会重新将捕获到的异常重新抛出
# 这种场景,一般都是用来做日志记录
try:
    1/0
except ZeroDivisionError:
    print("error")
    raise
# Traceback (most recent call last):
#   File "/usr/local/Project/cce/Py/edu.py", line 9, in <module>
#     1/0
# ZeroDivisionError: division by zero
# error

异常类及继承层次

    如下就是Python目前比较常见的异常类,以及继承的层次,各Python版本不同可能有变更,从BaseException基类开始,其他的都属于BaseException的子类,他们都是单一继承下来的,所以在Python中所有的异常都应该从BaseException开始,它是所有异常的基类。
    然后还分几大类,SystemExit、KeyboardInterrupt、GeneratorExit、Exception,其中SystemExit为系统退出,KeyboardInterrupt为键盘终止,类似Ctrl+C,GeneratorExit和协程Generator的关闭是有关系的,Exception,即异常,这是程序员在程序开发时,遇到的最常见的异常类的父类。
BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

继承捕获

    可以看到上面,异常类是有父子关系的,在我们使用execpt捕获的时候,如果是一个异常类下面的子异常类报错,也可以使用这个子异常类的父类去捕获,换而言之,如果抛出了一个父异常类下面的子异常类的异常,父异常类都可以捕获,如下示例。
try:
    print(1/0)
except ArithmeticError:  # 上述抛出的ZeroDivisionError异常,依旧可以被ArithmeticError捕获
    print("Error") # Error
    同时,对于except捕获,可以有多个,类似if语句一样,此处需要注意上述继承关系,因为except只会引发依次,也就是说即使有多个except,也只会执行其中一个,所以为了避免出现这种情况,我们应该把最里层的异常类放在最上面,而更大范围的父类放在最下面,由小到下依次往下。
try:
    raise Exception
except SystemExit:
    print("引发SystemExit")
except:
    print("引发except all") # 引发except all

SystemExit

    SystemExit是一个系统退出异常,但是这个SystemExit的异常有点特殊,它不会显式的抛出异常,所以用肉眼很难去判断这个异常,但是我们可以通过退出码来判断,即状态码,状态码如果非0,那么说明程序就是有问题的。
exit(100)
# Process finished with exit code 100

KeyboardInterrupt

    KeyboardInterrupt异常类,主要监听键盘时间,在程序运行时出现Ctrl+c的时候才会捕获到,那么一般这种异常,我们可以用来做数据清理,比如一个程序,运行到了一半,捕获到了Ctrl+c,那么此时,我们可以清理一些程序运行时的垃圾,然后终止程序,如下示例。
try:
    while True:
        name = input("input name: ")
except KeyboardInterrupt:
    print("-------Ctrl+c----------") # input name: -------Ctrl+c----------

Exception

    Exception是系统所有内建、非系统退出的异常基类,比如常见的StopIteration、KeyError以及IndexError等,同时,当我们需要自定义一个异常类时,可以从它开始继承。
try:
    print("1"+1)
except Exception:
    print("触发Exception") # 触发Exception

# 自定义异常类
class MyException(Exception):
    pass
try:
    raise MyException # 直接用自定义类引发
except MyException:
    print("触发MyException") # 触发MyException

as子句

    在异常语句的except阶段可以使用as子句来将一个异常类赋值给一个标识符,如果在异常处理阶段需要用到这个抛出的异常类,或者用到这个异常类的一些属性时,那么就可以使用as语法来将异常类保存到一个标识符下面,我们就可以利用这个标识符来获取类的属性,比如这个异常模块,就可以通过这个标识符拿到文件名或退出码等信息,然后利用标识符来引用。
try:
    open("cce","r")
except FileNotFoundError as e:
    print(e.filename,e.errno) # cce 2  # 前面是没有找到的文件名,后面是退出码

finally子句

    finally子句的意思是最终的意思,换而言之就是,不管是否触发异常,finally下面的代码一定会执行,并且finally在写的时候,是可以在except结合在一起的。
    但是在这里需要注意的是,try只捕获异常,如果在嵌套环境下,内层try将异常已经捕获了,那么外层的try将不会进行二次捕获。
try:
    open("cce","r")
except FileNotFoundError as e:
    print(e.filename,e.errno) # cce 2
finally:
    print('finally') # finally

# 升级版,嵌套异常处理,并做垃圾清理
try:
    fileobj=open("cce","r")
except FileNotFoundError as e:
    print(e.filename,e.errno) # cce 2
finally:
    try:
        fileobj.close()  # 不论fileobj是否成功拿到一个文件对象,都对其close
    except:
        pass

# 可以看到,外层error并没有打印,就说明,内层try捕获了,外层将不再进行二次捕获
try:
    try:
        1/0
    except:
        print("内层error")
    finally:
        print("内层finally")
except:
    print("外层error")
finally:
    print("外层finally")
# 内层error
# 内层finally
# 外层finally

else子句

    else子句存在于finally后面,except之前,else表示,如果try没有捕获到任何异常,else子句就会执行,如果捕获到异常就会进入到except,它是和except相反的,如下示例。
try:
    pass
except:
    print("error")
else:
    print("else")
finally:
    print("finally")
# else
# finally

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注