Python 核心编程(第二版)——错误和异常

1.什么是异常?

从软件方面来说, 错误是语法或是逻辑上的.语法错误指示软件的结构上有错误, 导致不能被解释器解释或编译器无法编译. 这些错误必须在程序执行前纠正.当程序的语法正确后, 剩下的就是逻辑错误了. 逻辑错误可能是由于不完整或是不合法的输入所致; 在其他情况下, 还可能是逻辑无法生成, 计算, 或是输出结果需要的过程无法执行. 这些错误通常分别被称为域错误和范围错误.

对异常的最好描述是: 它是因为程序出现了错误而在正常控制流以外采取的行为. 这个行为又分为两个阶段: 首先是引起异常发生的错误, 然后是检测(和采取可能的措施)阶段.

2.Python 中的异常

NameError: 尝试访问一个未申明的变量。

NameError 表示我们访问了一个没有初始化的变量,任何可访问的变量必须在名称空间里列出. 访问变量需要由解释器进行搜索,如果请求的名字没有在任何名称空间里找到, 那么将会生成一个 NameError 异常.

ZeroDivisionError: 除数为零。

SyntaxError: Python 解释器语法错误。

SyntaxError 异常是唯一不是在运行时发生的异常. 它代表 Python 代码中有一个不正确的结构, 在它改正之前程序无法执行。

IndexError:请求的索引超出序列范围。

IndexError 在你尝试使用一个超出范围的值索引序列时引发。

KeyError:请求一个不存在的字典关键字。

映射对象, 例如字典, 是依靠关键字(keys)访问数据值的. 如果使用错误的或是不存在的键请求字典就会引发一个 KeyError 异常。

IOError: 输入/输出错误

尝试打开一个不存在的磁盘文件一类的操作会引发一个操作系统输入/输出(I/O)错误. 任何类型的 I/O 错误都会引发 IOError 异常。

AttributeError: 尝试访问未知的对象属性

3.检测和处理异常

异常可以通过 try 语句来检测. 任何在 try 语句块里的代码都会被监测, 检查有无异常发生。try 语句有两种主要形式: try-except 和 try-finally . 这两个语句是互斥的, 也就是说你只能使用其中的一种. 一个 try 语句可以对应一个或多个 except 子句, 但只能对应一个finally 子句, 或是一个 try-except-finally 复合语句。

你可以使用 try-except 语句检测和处理异常. 你也可以添加一个可选的 else 子句处理没有探测到异常的时执行的代码. 而 try-finally 只允许检测异常并做一些必要的清除工作(无论发生错误与否), 没有任何异常处理设施. 正如你想像的,复合语句两者都可以做到.

try-except 语句(以及其更复杂的形式)定义了进行异常监控的一段代码, 并且提供了处理异常的机制。最常见的 try-except 语句语法如下所示. 它由 try 块和 except 块(try_suite 和except_suite )组成, 也可以有一个可选的错误原因。

try:
    try_suite # watch for exceptions here 监控这里的异常
except Exception[, reason]:
    except_suite # exception-handling code 异常处理代码

在程序运行时, 解释器尝试执行 try 块里的所有代码, 如果代码块完成后没有异常发生, 执行流就会忽略 except 语句继续执行. 而当 except 语句所指定的异常发生后, 我们保存了错误的原因, 控制流立即跳转到对应的处理器( try 子句的剩余语句将被忽略)。

核心笔记:忽略代码, 继续执行, 和向上移交

try 语句块中异常发生点后的剩余语句永远不会到达(所以也永远不会执行). 一旦一个异常被引发, 就必须决定控制流下一步到达的位置. 剩余代码将被忽略, 解释器将搜索处理器, 一旦找到,就开始执行处理器中的代码。如果没有找到合适的处理器, 那么异常就向上移交给调用者去处理, 这意味着堆栈框架立即回到之前的那个. 如果在上层调用者也没找到对应处理器, 该异常会继续被向上移交, 直到找到合适处理器. 如果到达最顶层仍然没有找到对应处理器, 那么就认为这个异常是未处理的, Python 解释器会显示出跟踪返回消息, 然后退出.

except 语句在处理多个异常时要求异常被放在一个元组里:

except (Exception1, Exception2)[, reason]:
    suite_for_Exception1_and_Exception2
except (Exc1[, Exc2[, ... ExcN]])[, reason]:
    suite_for_exceptions_Exc1_to_ExcN

核心风格: 不要处理并忽略所有错误

Python 提供给程序员的 try-except 语句是为了更好地跟踪潜在的错误并在代码里准备好处理异常的逻辑.它的目的是减少程序出错的次数并在出错后仍能保证程序正常执行. 作为一种工具而言, 只有正确得当地使用它, 才能使其发挥作用.

底线: 避免把大片的代码装入 try-except 中然后使用 pass 忽略掉错误. 你可以捕获特定的异常并忽略它们, 或是捕获所有异常并采取特定的动作. 不要捕获所有异常,然后忽略掉它们.

一个最终的注意点:如果finally 中的代码引发了另一个异常或由于return,break,continue 语法而终止,原来的异常将丢失而且无法重新引发.

所有不同的可以处理异常的语法样式:

try:
    try_suite

except Exception1:
    suite_for_Exception1

except (Exception2, Exception3, Exception4):
    suite_for_Exceptions_2_3_and_4

except Exception5, Argument5:
    suite_for_Exception5_plus_argument

except (Exception6, Exception7), Argument67:
    suite_for_Exceptions6_and_7_plus_argument

except:
    suite_for_all_other_exceptions

else:
    no_exceptions_detected_suite

finally:
    always_execute_suite

4.上下文管理 

with 语法的基本用法看上去如下:

with context_expr [as var]:
    with_suite

它仅能工作于支持上下文管理协议(context management protocol)的对象.这显然意味着只有内建了"上下文管理"的对象可以和with 一起工作.目前已经有了一些支持该协议的对象.下面是第一批成员的简短列表:

􀁺 file
􀁺 decimal.Context
􀁺 thread.LockType
􀁺 threading.Lock
􀁺 threading.RLock
􀁺 threading.Condition
􀁺 threading.Semaphore
􀁺 threading.BoundedSemaphore

上下文表达式(context_expr),上下文管理器

当with 语句执行时,便执行上下文符号来获得一个上下文管理器.上下文管理器的职责是提供一个上下文对象.这是通过调用__context__()方法来实现的.该方法返回一个上下文对象,用于在with 语句块中处理细节.需要注意的是上下文对象本身就可以是上下文管理器.所以context_expr 既可以为一个真正的上下文管理器,也可以是一个可以自我管理的上下文对象.在后一种情况时,上下文对象仍然有__context__()方法,返回其自身,如你所想.

上下文对象,with 语句块

一旦我们获得了上下文对象,就会调用它的__enter()__方法.它将完成with 语句块执行前的所有准备工作.你可以注意到在上面的with 行的语法中有一个可选的as 声明变量跟随在context_expr之后.如果提供提供了变量,以__enter()__返回的内容来赋值;否则,丢弃返回值.

执行了with 语句块.当with 语句块执行结束,无论是"和谐地"还是由于异常,都会调用上下文对象的__exit()__方法.__exit__()有三个参数.如果with 语句块正常结束,三个参数全部是None.如果发生异常,三个参数的值的分别等于调用sys.exc_info()函数返回的三个值:类型(异常类),值(异常实例),和回溯(traceback),相应的回溯对象.

可以自己决定如何在__exit__()里面处理异常.惯例是当你处理完异常时不返回任何值,或返回None,或返回其他布尔值为False 对象.这样可以使异常抛给你的用户来处理.如果你明确的想屏蔽这个异常,返回一个布尔为True 的值.如果没有发生异常或你在处理异常后返回True,程序会继续执行with 子句后的下一段代码.

因为上下文管理器主要作用于共享资源,你可以想象到__enter()__和__exit()__方法基本是干的需要分配和释放资源的低层次工作。

5. 触发异常

raise 语句对所支持是参数十分灵活,对应到语法上就是支持许多不同的格式.rasie 一般的用法是:

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

第一个参数,SomeExcpetion,是触发异常的名字.如果有,它必须是一个字符串,类或实例(详见下文).如果有其他参数(arg 或traceback),就必须提供SomeExcpetion.Python 所有的标准异常。

第二个符号为可选的args(比如参数,值),来传给异常.这可以是一个单独的对象也可以是一个对象的元组.当异常发生时,异常的参数总是作为一个元组传入.如果args 原本就是元组,那么就将其传给异常去处理;如果args 是一个单独的对象,就生成只有一个元素的元组(就是单元素元组).大多数情况下,单一的字符串用来指示错误的原因.如果传的是元组,通常的组成是一个错误字符串,一个错误编号,可能还有一个错误的地址,比如文件,等等.

最后一项参数,traceback,同样是可选的(实际上很少用它),如果有的话,则是当异常触发时新生成的一个用于异常-正常化(exception—normally)的追踪(traceback)对象.当你想重新引发异常时,第三个参数很有用(可以用来区分先前和当前的位置).如果没有这个参数,就填写None.

最常见的用法为SomeException 是一个类.不需要其他的参数,但如果有的话,可以是一个单一对象参数,一个参数的元组,或一个异常类的实例.如果参数是一个实例,可以由给出的类及其派生类实例化(已存在异常类的子集).若参数为实例,则不能有更多的其他参数.

raise 语句的用法
raise 语法 描述
raise exclass 触发一个异常,从exclass 生成一个实例(不含任何异常参数)
raise exclass() 同上,除了现在不是类;通过函数调用操作符(function calloperator:"()")作用于类名生成一个新的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 string (过时的) 触发字符串异常
raise string, args 同上,但触发伴随着args
raise string, args, tb 同上,但提供了一个追踪(traceback)对象tb 供使用
raise (1.5 新增)重新触发前一个异常,如果之前没有异常,触发TypeError.

6.断言

断言是一句必须等价于布尔真的判定;此外,发生异常也意味着表达式为假.断言可以简简单单的想象为raise-if 语句(更准确的说是raise-if-not 语句).测试一个表达式,如果返回值是假,触发异常.断言通过assert 语句实现,在1.5 版中引入.

断言语句等价于这样的Python 表达式,如果断言成功不采取任何措施(类似语句),否则触发AssertionError(断言错误)的异常.assert 的语法如下:

assert expression[, arguments]

AssertionError 异常和其他的异常一样可以用try-except 语句块捕捉,但是如果没有捕捉,它将终止程序运行而且提供一个如下的traceback:

1 >>> assert 1 == 0
2 Traceback (innermost last): File "<stdin>", line 1, in ?
3 AssertionError
View Code

为了让你更加了解assert 如何运作,想象一下断言语句在Python 中如何用函数实现.可以像下面这样:

def assert(expr, args=None):
    if __debug__ and not expr:
        raise AssertionError, args

此处的 if 语句检查 assert 的语法是否合适,也就是expr 必须是一个表达式.我们比较expr的类型和真正的表达式来确认.函数的第二部分对表达式求值然后根据结果选择性的引发异常.内建的变量__debug__在通常情况下为True,如果开启优化后为False(命令行选项-O)(Python 2.2 后为布尔值True 和False.)

7.标准异常

Python 内建异常
异常名称 描述
BaseException 所有异常的基类
SystemExit python 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
SystemExit Python 解释器请求退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError Windows 系统调用失败
ImportError 导入模块/对象失败
KeyboardInterrupt 用户中断执行(通常是输入^C)
LookupError 无效数据查询的基类
IndexError 序列中没有没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalErrorh 访问未初始化的本地变量
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 用户代码生成的警告

所有的标准/内建异常都是从根异常派生的.目前,有3 个直接从BaseException 派生的异常子类:SystemExit,KeyboardInterrupt 和Exception.其他的所有的内建异常都是Exception 的子类.

8. 创建异常

IOError 是一个用于输入/输出的通用异常,可能在无效的文件访问或其他形式的通信中触发.

9.  异常和sys模块

另一种获取异常信息的途径是通过sys 模块中exc_info()函数. 此功能提供了一个3 元组(3-tuple)的信息, 多于我们单纯用异常参数所能获得.从sys.exc_info()得到的元组中是:

􀁺 exc_type: 异常类
􀁺 exc_value: 异常类的实例
􀁺 exc_traceback: 追踪(traceback)对象

异常相关的标准库
模块 描述
exceptions 内建异常(永远不用导入这个模块)
contextliba 为使用with语句的上下文对象工具
sys 包含各种异常相关的对象和函数

 

posted @ 2017-10-18 18:49  Christal_11  阅读(309)  评论(0编辑  收藏  举报