第32~33讲:异常处理:你不可能总是对的

一 异常处理相关内容

1 定义:

  • 异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。
  • 一般情况下,在Python无法正常处理程序时就会发生一个异常。
  • 异常是Python对象,表示一个错误。
  • 当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。

2 错误分类:

  • 语法错误:就是解析代码时出现的错误。
    • 当代码不符合 Python 语法规则时,Python解释器在解析时就会报出 SyntaxError 语法错误,与此同时还会明确指出最早探测到错误的语句。
  • 运行时错误:程序在语法上都是正确的,但在运行时发生了错误。
    • 在 Python 中,把这种运行时产生错误的情况叫做异常(Exceptions)
    • 报错内容:
      • 大多数的异常都不会被程序处理,在程序运行结束后,会以错误信息的形式返回给用户
      • 异常以不同的类型出现,这些类型都作为信息的一部分打印出来: 例子中的类型有 ZeroDivisionError,NameError 和 TypeError。
      • 错误信息的前面部分显示了异常发生的上下文,并以调用栈的形式显示具体信息。

3 异常处理机制

  • 定义:就是在程序运行出现错误时,让 Python 解释器执行事先准备好的除错程序,进而尝试恢复程序的执行。
  • 内容:Python 异常处理机制会涉及 try、except、else、finally 这 4 个关键字,同时还提供了可主动使程序引发异常的 raise 语句(Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常)。

4 捕获异常详解

  • try/except
    • 功能:捕捉异常——try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。
    • 语法如下:
      • 1 try:
        2     可能产生异常的代码块
        3 except [ (Error1, Error2, ... ) [as e] ]:
        4     处理异常的代码块1
        5 except [ (Error3, Error4, ... ) [as e] ]:
        6     处理异常的代码块2
        7 except  [Exception]:
        8     处理其它异常
      • 从try except的基本语法格式可以看出,try 块有且仅有一个,但 except 代码块可以有多个,且每个 except 块都可以同时处理多种异常。
      • 在 Python 2.x 的早期版本中,除了使用 as e 这个格式,还可以将其中的 as 用逗号(,)代替。
    • 图解:
    • 参数分析:[] 括起来的部分可以使用,也可以省略
      • (Error1, Error2,...) 、(Error3, Error4,...):其中,Error1、Error2、Error3 和 Error4 都是具体的异常类型。显然,一个 except 块可以同时处理多种异常。
      • [as e]:作为可选参数,表示给异常类型起一个别名 e,这样做的好处是方便在 except 块中调用异常类型(后续会用到)。
      • [Exception]:作为可选参数,可以代指程序可能发生的所有异常情况,其通常用在最后一个 except 块。
    • 执行流程:
      • 首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)。
      • 如果没有异常发生,忽略 except 子句,try 子句执行后结束。
      • 如果在执行 try 子句的过程中出现异常,系统会自动生成一个异常类型,并将该异常提交给 Python 解释器,此过程称为捕获异常
      • 当 Python 解释器收到异常对象时,会寻找能处理该异常对象的 except 块,如果找到合适的 except 块,则把该异常对象交给该 except 块处理,这个过程被称为处理异常。如果 Python 解释器找不到处理异常的 except 块(未被成功处理),则程序运行终止,Python 解释器也将退出。
    • 获取特定异常的有关信息(调用下面函数或方法):
      • args:返回异常的错误编号和描述字符串;
      • str(e):返回异常信息,但不包括异常信息的类型;
      • repr(e):返回较全的异常信息,包括异常信息的类型。
      • 除此之外,如果想要更加详细的异常信息,可以使用 traceback 模块。
  • try/except...else
    • 功能:使用 else 包裹的代码,只有当 try 块没有捕获到任何异常时,才会得到执行;反之,如果 try 块捕获到异常,即便调用对应的 except 处理完异常,else 块中的代码也不会得到执行。
    • 语法:
      •  1 try:
         2     可能产生异常的代码块
         3 except [ (Error1, Error2, ... ) [as e] ]:
         4     处理异常的代码块1
         5 except [ (Error3, Error4, ... ) [as e] ]:
         6     处理异常的代码块2
         7 except  [Exception]:
         8     处理其它异常
         9 else:
        10     没有异常时执行的代码
    • 图解:
    • 特点:else 的功能,只有当 try 块捕获到异常时才能显现出来。在这种情况下,else 块中的代码不会得到执行的机会。可以提高执行效率
  • try except finally
    • 功能:Python 异常处理机制还提供了一个 finally 语句,通常用来为 try 块中的程序做扫尾清理工作。
      • Python 垃圾回收机制,只能帮我们回收变量、类对象占用的内存,而无法自动完成类似关闭文件、数据库连接等这些的工作。
    • finally语句:
      • 功能:无论 try 块是否发生异常,最终都要进入 finally 语句,并执行其中的代码块。
      • 特点:finally要求和 try 搭配使用,而至于该结构中是否包含 except 以及 else,对于 finally 不是必须的(else 必须和 try except 搭配使用)。
    • 语法:
      •  1 try:
         2     可能产生异常的代码块
         3 except [ (Error1, Error2, ... ) [as e] ]:
         4     处理异常的代码块1
         5 except [ (Error3, Error4, ... ) [as e] ]:
         6     处理异常的代码块2
         7 except  [Exception]:
         8     处理其它异常
         9 else:
        10     没有异常时执行的代码
        11 finally:
        12     不管有没有异常都会执行的代码
    • 图解:
    • 为什么是finally语句进行资源回收:
      • 首先,try 块不适合做资源回收工作,因为一旦 try 块中的某行代码发生异常,则其后续的代码将不会得到执行;
      • 其次 except 和 else 也不适合,它们都可能不会得到执行。
      • 而 finally 块中的代码,无论 try 块是否发生异常,该块中的代码都会被执行。
  • 获取更多的异常信息
    • 使用 sys 模块中的 exc_info 方法;
      • 模块 sys 中,有两个方法可以返回异常的全部信息,分别是 exc_info() 和 last_traceback(),这两个函数有相同的功能和用法,本节仅以 exc_info() 方法为例。
      • exc_info() 方法会将当前的异常信息以元组的形式返回,该元组中包含 3 个元素,分别为 type、value 和 traceback
        • type:异常类型的名称,它是 BaseException 的子类
        • value:捕获到的异常实例。
        • traceback:是一个 traceback 对象。
    • 使用 traceback 模块中的相关函数。
      •  traceback 模块可以用来查看异常的传播轨迹,追踪异常触发的源头。
      • 使用 traceback 模块查看异常传播轨迹,首先需要将 traceback 模块引入,该模块提供了如下两个常用方法:
        • traceback.print_exc():将异常传播轨迹信息输出到控制台或指定文件中。完整形式:print_exc([limit[, file]]) 
          • 使用 print_exc([limit[, file]]) 会自动处理当前 except 块所捕获的异常
          • limit:用于限制显示异常传播的层数,比如函数 A 调用函数 B,函数 B 发生了异常,如果指定 limit=1,则只显示函数 A 里面发生的异常。如果不设置 limit 参数,则默认全部显示。
          • file:指定将异常传播轨迹信息输出到指定文件中。如果不指定该参数,则默认输出到控制台。
        • format_exc():将异常传播轨迹信息转换成字符串。完整形式:print_exception(etype, value, tb[,limit[, file]])
          • etype:指定异常类型;value:指定异常值;tb:指定异常的traceback 信息;
          • 简单来说, print_exc([limit[, file]]) 相当于该形式:print_exception(sys.exc_etype, sys.exc_value, sys.exc_tb[, limit[, file]])
          • 当程序处于 except 块中时,该 except 块所捕获的异常信息可通过 sys 对象来获取,其中 sys.exc_type、sys.exc_value、sys.exc_traceback 就代表当前 except 块内的异常类型、异常值和异常传播轨迹。

5 抛出异常

  • raise
    • 功能:Python 使用 raise 语句抛出一个指定的异常。
    • 语法:
      • raise [Exception [, args [, traceback]]]

        语句中 Exception 是异常的类型(例如,NameError)参数标准异常中任一种,args 是自已提供的异常参数。最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。

      • raise [exceptionName [(reason)]]

        其中,用 [] 括起来的为可选参数,其作用是指定抛出的异常名称,以及异常信息的相关描述。如果可选参数全部省略,则 raise 会把当前错误原样抛出;如果仅省略 (reason),则在抛出异常时,将不附带任何的异常描述信息。

    • 图解:
    • 常用用法:
      • raise:单独一个 raise。该语句引发当前上下文中捕获的异常(比如在 except 块中),或默认引发 RuntimeError 异常。
      • raise 异常类名称:raise 后带一个异常类名称,表示引发执行类型的异常。
      • raise 异常类名称(描述信息):在引发指定类型的异常的同时,附带异常的描述信息

6 常见异常类型

异常名称描述
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告

 

7 参考内容:

二 课后作业

第32讲:

0. 结合你自身的编程经验,总结下异常处理机制的重要性?

答:由于环境的不确定性和用户操作的不可以预知性都可能导致程序出现各种问题,因此异常机制最重要的无非就是:增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序自动消化并恢复正常(不至于崩溃)。

1. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

>>> my_list = [1, 2, 3, 4,,]

答:语法错误
SyntaxError: invalid syntax^ZHA!

2. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

>>> my_list = [1, 2, 3, 4, 5]
>>> print(my_list[len(my_list)])
 

答:len(my_list) 是获得列表的长度,这里长度为5,该列表各个元素的访问索引号分别是:[0, 1, 2, 3, 4],因此试图访问 my_list(5) 会引发 IndexError: list index out of range 异常。
52%W)pRNJrBt1E#IF*Cw
3. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

>>> my_list = [3, 5, 1, 4, 2]
>>> my_list.sorted()

答:初学者容易疏忽的错误,列表的排序方法叫 list.sort(),sorted() 是BIF。因此会引发 AttributeError: 'list' object has no attribute 'sorted' 异常。

4. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

>>> my_dict = {'host': 'http://bbs.fishc.com', 'port': '80'}
>>> print(my_dict['server'])

答:尝试访问字典中一个不存在的“键”引发 KeyError: 'server' 异常,为了避免这个异常发生,可以使用 dict.get() 方法:

if not my_dict.get('server'):
        print('您所访问的键【server】不存在!')

5. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

1 def my_fun(x, y):
2     print(x, y)
3 
4 my_fun(x=1, 2)

答:如果使用关键字参数的话,需要两个参数均使用关键字参数 my_fun(x=1, y=2)

6. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:GJS{F

1 f = open('C:\\test.txt', wb)
2 f.write('I love FishC.com!\n')
3 f.close()

答:注意 open() 第二个参数是字符串,应该 f = open('C:\\test.txt', 'wb') 。wb不加双引号 Python 还以为是变量名呢,往上一找,艾玛没找着……引发 NameError 异常。由于打开文件失败,接着下边一连串与 f 相关的均会报同样异常。

7. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

1 def my_fun1():
2         x = 5
3         def my_fun2():
4                 x *= x
5                 return x
6         return my_fun2()
7 
8 my_fun1()

答:闭包的知识大家还记得不? Python 认为在内部函数的 x 是局部变量的时候,外部函数的 x 就被屏蔽了起来,所以执行 x *= x 的时候,在右边根本就找不到局部变量 x 的值,因此报错。
在 Python3 之前没有直接的解决方案,只能间接地通过容器类型来存放,因为容器类型不是放在栈里,所以不会被“屏蔽”掉。容器类型这个词儿大家是不是似曾相识?我们之前介绍的字符串、列表、元祖,这些啥都可以往里的扔的就是容器类型啦。
于是乎我们可以把代码改造为:

1 def my_fun1():
2         x = [5]
3         def my_fun2():
4                 x[0] *= x[0]
5                 return x[0]
6         return my_fun2()
7 
8 my_fun1()

但是到了 Python3 的世界里,又有了不少的改进,如果我们希望在内部函数里可以修改外部函数里的局部变量的值,那么也有一个关键字可以使用,就是 nonlocal:

1 def my_fun1():
2         x = 5
3         def my_fun2():
4                 nonlocal x
5                 x *= x
6                 return x
7         return my_fun2()
8 
9 my_fun1()

 

第33讲:

测试题:

0. 我们使用什么方法来处理程序中出现的异常?M2_<pA-ox

答:使用 try……except 搭配来捕获处理程序中出现的异常。
B
1. 一个 try 语句可以和多个 except 语句搭配吗?为什么?
答:可以。因为 try 语句块中可能出现多类异常,利用多个 except 语句可以分别捕获并处理我们感兴趣的异常。

1 try:
2     sum = 1 + '1'
3     f = open('我是一个不存在的文档.txt')
4     print(f.read())
5     f.close()
6 except OSError as reason:
7     print('文件出错啦T_T\n错误原因是:' + str(reason))
8 except TypeError as reason:
9     print('类型出错啦T_T\n错误原因是:' + str(reason))

 

2. 你知道如何统一处理多类异常吗?
答:在 except 后边使用小括号“()”把多个需要统一处理的异常括起来:

1 try:
2     int('abc')
3     sum = 1 + '1'
4     f = open('我是一个不存在的文档.txt')
5     print(f.read())
6     f.close()
7 except (OSError, TypeError):
8     print('出错啦T_T\n错误原因是:' + str(reason))


3. except 后边如果不带任何异常类,Python 会捕获所有(try 语句块内)的异常并统一处理,但小甲鱼却不建议这么做,你知道为什么吗?
答:因为它会隐藏所有程序员未想到并且未做好准备处理的错误,例如用户输入ctrl+c试图终止程序会被解释为KeyboardInterrupt异常。IUADmZE4<

4. 如果异常发生在成功打开文件后,Python 跳到 except 语句执行,并没有执行关闭文件的命令(用户写入文件的数据就可能没有保存起来),因此我们需要确保无论如何(就算出了异常退出)文件也要被关闭,我们应该怎么做呢?
答:我们可以使用 finally 语句来实现,如果 try 语句块中没有出现任何运行时错误,会跳过 except 语句块执行 finally 语句块的内容。
如果出现异常,则会先执行 except 语句块的内容再接着执行 finally 语句块的内容。总之,finally 语句块里的内容就是确保无论如何都将被执行的内容!

5. 请恢复以下代码中马赛克挡住的内容,使得程序执行后可以按要求输出。9@BFXV~J
答:这道题比较考脑瓜,你想到了吗?

1 try:
2     for i in range(3):
3         for j in range(3):
4             if i == 2:
5                 raise KeyboardInterrupt
6             print(i, j)
7 except KeyboardInterrupt:
8     print('退出啦!')

 

posted @ 2020-08-05 00:07  洛兰123  阅读(84)  评论(0编辑  收藏