python 异常处理

目录

  • python 异常处理
    • 目的
    • 基本异常语句
        • try 语句基本用法
    • 异常传播
        • 先读懂报错信息
            • 异常代码出现在全局时
            • 异常代码出现在函数内
        • 异常传播的概念
    • 异常对象
        • 概念
        • try 语句捕获异常对象
        • 举个栗子
    • 抛出异常语句
        • raise 语句用法
        • 发现问题
        • 一个小测试和小结论和小猜想
        • 验证猜想:raise 语句的更好用法
        • 剩下一点瑕疵
    • 自定义异常类

python 异常处理

本文部分内容整理自:阿里云大学 - python学习路线 - 课时42~45

目的

避免程序在运行中,因为一些异常错误(例如一个数除以0、打印未知变量等)而出现闪退、崩溃等立即停止的情况。
因此,针对有可能出错的代码,我们需要实现以下效果:

  1. 当这段代码不会发生错误时,正常执行。
  2. 当这段代码发生错误时,汇报错误并继续执行。

基本异常语句

try 语句基本用法

try:
	代码块(可能出现错误的语句)
except:
	代码块(出现错误后的处理方式)
else:
	代码块(未出现错误后的处理方式,可选)
finally:
	代码块(无论是否出现错误都会执行,和'except'至少有一个)

注意:如果 try 语句里有多个异常,except 会在第一个异常发生时进行处理。

异常传播

一段代码在执行的过程中,异常既可能出现在全局,也可能出现在函数内。当异常出现在函数内,并且我们调用了这个函数时(不调用就不存在异常了),就会发生异常的传播。

先读懂报错信息

异常代码出现在全局时
print(2/0)
Traceback (most recent call last):
  File "D:\code\test.py", line 1, in <module>
    print(2/0)
ZeroDivisionError: division by zero
  1. 第一行,Traceback 是回溯的意思(追溯异常的位置),出现 Traceback 表示出现异常了。
  2. 第二行,显示错误在哪个文件,哪一行,哪个函数/类(module表示在全局内)。
  3. 第三行,显示出错的代码。
  4. 第四行,显示错误类型(即异常对象的名字),错误的详细信息。
    (为了方便理解报错信息的结构,这里的第几行,仅表示顺序)
异常代码出现在函数内
def fun():
    print(2/0)
fun()
Traceback (most recent call last):
  File "D:\code\test.py", line 3, in <module>
    fun()
  File "D:\code\test.py", line 2, in fun
    print(2/0)
ZeroDivisionError: division by zero

我们可以看到,python会显示两处错误的地方,第一处是调用产生错误的函数时,第二处是函数内出错的地方。

异常传播的概念

当在函数中出现异常时,如果在函数/类中对异常进行了处理,则异常不会再继续传播。
如果函数中没有对异常进行处理,则异常会继续向函数调用处传播。
如果函数调用处处理了异常,则不再传播,否则异常继续向调用处传播。
直到传递到全局作用域(主模块),如果依然没有处理,则程序终止并显示异常信息。

def fun1():
    print(2/0)

def fun2():
    fun1()

def fun3():
    fun2()

fun3()

这段代码中,异常在 fun1() 中发生,会依次传递到 fun2() ,fun3() ,最后进入全局。按从下往上的顺序产生以下报错。(可以理解为最下面的是最内层的错误)

Traceback (most recent call last):
  File "D:\code\test.py", line 10, in <module>
    fun3()
  File "D:\code\test.py", line 8, in fun3
    fun2()
  File "D:\code\test.py", line 5, in fun2
    fun1()
  File "D:\code\test.py", line 2, in fun1
    print(2/0)
ZeroDivisionError: division by zero

注:类里的语句会在导入类所在模块时自动运行,所以报错也会提前到导入当前模块时(主模块就是直接运行时)。

异常对象

概念

当程序运行过程中出现异常以后,所有的异常信息会被保存到一个专门的异常对象中。异常传播,实际上就是把异常对象抛给调用处。
在前面的例子中,ZeroDivisionError 就是一个异常类。
python 中为我们提供了多个异常类,可以在 python 的 documentation 文档中,Library Reference 目录下的 Built-in Exceptions 中查询所有异常类。
异常对象就是异常类的实例。

try 语句捕获异常对象

这里,我们拓展之前 try 语句基本用法里 except 语句的用法。
except :except Exception :
如果 except 后面不跟任何内容,此时它会捕获到所有异常。(Exception是所有异常的父类,因此也可以捕获所有异常)
except 某个异常类名 :(例如:except ZeroDivisionError : )
如果 except 后面跟着某一个异常的类型,此时它只会捕获到该异常的类型。
注意:如果此时代码中有其他异常,并且其他异常比我们想捕获的异常更早发生,则程序也会立即终止。
注意:如果使用多个“ except 某个异常类名 : ”语句,则可以做到捕获多个不同的异常。 但也只会处理第一个发生的异常。为了防止出现你不知道的异常,也可以在最后加一个捕获所有异常的语句。
except 某个异常类名 as xx :
except … as … 语句可以捕获到异常对象并赋值给 xx 。

举个栗子

try:
    print(1+'1')
    print(a)
    print(2/0)
except ZeroDivisionError as e:
    print(e)
except NameError as e:
    print(e)
except Exception as e:
    print('new exception:',e)

结果为:

new exception: unsupported operand type(s) for +: ‘int’ and ‘str’

抛出异常语句

假设我们自定义了一个函数或方法,且对其输入的参数有一定要求。当输入的参数不是我们想要的参数类型时,我们可以主动抛出一个异常来阻止程序的运行。
我们可以使用 raise 语句来主动抛出一个异常。

raise 语句用法

raise 异常类或异常对象

例如:

raise Exception

返回结果为:

Traceback (most recent call last):
  File "D:\code\test.py", line 1, in <module>
    raise Exception
Exception

发现问题

我们来结合一下上面学到的 try 语句。

try:
    raise Exception
except Exception as e:
    print(e)

当我们运行这个语句时,发现一个问题。运行结果竟然只有一个空行!这就很难受了。必须想想办法。

一个小测试和小结论和小猜想

结论:直接用上面的 raise 语句产生的异常对象调用 str() 函数,返回值为空字符串,因此无法直接在捕获时用 print() 函数打印出错误信息。调用 repr() 函数返回值不为空字符串。测试如下:

try:
    raise NameError
except Exception as e:
    print(type(e.__repr__()),e.__repr__(),sep=':')
    print(type(e.__str__()),e.__str__(),sep=':')

:NameError()
:

猜想:实际中使用 raise 语句时,后面的类应该传入一些参数生成有具体信息的实例。

验证猜想:raise 语句的更好用法

其实,我们只需要给 raise 后面的异常类传入一个字符串参数,就能生成一个带打印信息的异常对象。

try:
    raise Exception('发生了一个故意发生的错误')
except Exception as e:
    print(e)

结果为:

发生了一个故意发生的错误

我们再来看一下直接使用这个语句的错误信息。

Traceback (most recent call last):
  File "D:\code\test.py", line 7, in <module>
    raise Exception('发生了一个故意发生的错误')
Exception: 发生了一个故意发生的错误

剩下一点瑕疵

到此为止,我们的代码已经基本满足了我们的需求,但是还有一个令人不舒服的地方:我们到现在用的都是系统里已有的类,或者是 Exception 这种不够精确的异常类,而针对我们自定义的函数/或方法,我们肯定希望能有一个异常类能直观、清楚地表现,这个异常是因为错误调用我们的自定义函数/方法而产生的。目前的代码,显然不够完美,因此,我们需要一个自定义异常类。

自定义异常类

现在,我们已经知道了 Exception 这个类是所有异常类的父类。因此我们在自定义异常类的时候,必须继承 Exception 这个类。
实际上,很多时候,我们只需要自定义一个继承 Exception 类的异常类即可。

class MyError(Exception):
	pass

当然,如果我们想自己完善这个类也可以。
例如我们定义一个函数,这个函数只支持整数变量传入,当我们传入非整数变量时,会抛出我们的一个自定义异常类。这样我们又需要定义一个异常类,这个类可以传入一个不为整数的参数,并且报错信息会告诉你,你传入的参数是什么,是什么类型,而我们需要传入一个整数。
代码如下:

class MyError(Exception):
    def __init__(self,data):
        self.data = data
    def __str__(self):
        error_message = ('Error:你传入的变量是'+repr(self.data)+',是'
        +repr(type(self.data))+'变量,而需要传入int变量')
        return error_message

def myfunction(data):
    if type(data) != int:
        raise MyError(data)
    else:
        print('已收到:%d'%data)

if __name__ == '__main__':
    myfunction('1')

运行结果为:

Traceback (most recent call last):
  File "D:\code\test.py", line 21, in <module>
    myfunction('1')
  File "D:\code\test.py", line 11, in myfunction
    raise MyError(data)
MyError: Error:你传入的变量是'1',是<class 'str'>变量,而需要传入int变量

(自己写代码时遇到的一个小易错点:一堆字符串相加生成新字符串时,换行必须让加号在最前面,且必须在整个外面加上小括号,如代码第5、6行。如果换行加号不放在换行后的前面会编译不通过,如果外面不加小括号会产生如下很神奇的报错)

Traceback (most recent call last):
  File "D:\code\test.py", line 17, in <module>
    myfunction('1')
  File "D:\code\test.py", line 11, in myfunction
    raise MyError(data)
MyError: <unprintable MyError object>

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "D:\code\test.py", line 19, in <module>
    print(e)
  File "D:\code\test.py", line 6, in __str__
    +repr(type(self.data))+'变量,而需要传入int变量'
TypeError: bad operand type for unary +: 'str'

如果你的报错信息中有 During handling of the above exception, another exception occurred:
,说明你的异常类在报错时发生错误。

你可能感兴趣的:(python,基础知识,python)