6-2-异常编码细节
1. try/except/else 语句
try 是复合语句,它的最完整的形式如下:
try:
<statements>
except <name1>:
<statements>
except (name2, name3):
<statements>
except <name4> as <data>:
<statements>
except:
<statements>
else:
<statements>
1.1 语句的运行方式
- 如果 try 代码块语句执行时的确发生了异常,Python 就跳回 try,执行第一个符合引发异常的 except 子句下面的语句。当 except 代码块执行后,控制权就会到整个 try 语句后继续执行。
- 如果异常发生在 try 代码块内,没有符合的 except 子句,异常就会向上传递到程序中的之前进入的 try 中,或者如果它是第一条这样的语句,就传递到这个进程的顶层,Python 会终止程序并打印默认出错消息。
- 如果 try 首行底下执行的语句没有发生异常,Python 就会执行 else 行下的语句,控制权会在整个 try 语句下继续。
1.2 try 语句分句
分句形式 | 说明 |
---|
except:|捕捉所有(其他)异常类型
except name:|只捕捉特定的异常
except name as value:|捕捉所列的异常并赋值其实例
except (name1, name2):|捕捉任何列出的异常
except (name1, name2) as value:|捕捉任何列出的异常并赋值其实例
else:|如果没有引发异常,就运行
finally:|总是会运行此代码块
如果想要编写通用的“捕捉一切”分句,空的 except 就可以做到。尽管方便,空 except 也可能捕捉和程序代码无关、意料之外的系统异常,而且可能意外拦截其他处理器的异常。
Python 3.X 引入了一个替代方案来解决这些问题之一——捕获一个名为 Exception 的异常几乎与一个空的 except 具有相同的效果,但是忽略和系统退出相关的异常:
try:
action()
except Exception:
...
1.3 try/else 分句
没有 else,是无法知道控制流程是否已经通过了 try 语句,因为没有异常引发或者因为异常发生了且已被处理过。
2. try/finally 语句
- 如果 try 代码块运行时没有异常发生,Python 会跳至执行 finally 代码块,然后在整个 try 语句后继续执行下去。
- 如果 try 代码块运行时有异常发生,Python 依然会回来运行 finally 代码块,但是接着会把异常向上传递到较高的 try 语句或顶层默认处理器。程序不会在 try 语句下继续运行。
finally 可以和 except 及 else 出现在相同语句内。
3. 统一 try/except/finally 语句
try:
main-action
except Exception1:
handler1
except Exception2:
handler2
...
else:
else-block
finally:
finally-block
即使异常处理器或者 else-block 内有错误发生而引发了新的异常,finally-block 内的程序代码依然会执行。
3.1 统一 try 语句语法
方括号表示可选,星号表示0个或多个:
# format1
try:
statements
except [type [as value]]:
statements
[except [type [as value]]:
statements]*
[else:
statements]
[finally:
statements]
# format2
try:
statements
finally:
statements
至少有一个 except 的时候,else 才能够出现,并且总是可能混合 except 和 finally,而不管是否有一个 else。也可能混合 finally 和 else,但只在 except 也出现的时候。
4. raise 语句
要显式地触发异常,可以使用 raise 语句。raise 关键字,后面跟着可选的要引发的类或者类的一个实例。
4.1 引发异常
对于内置异常,如下两种形式是对等的,都会引发指定的异常类的一个实例,但是第一种形式隐式地创建实例:
raise IndexError # 类
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-1-55a00e7db5b5> in <module>()
----> 1 raise IndexError
IndexError:
raise IndexError() # 实例
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-2-7811308d6908> in <module>()
----> 1 raise IndexError()
IndexError:
当引发一个异常的时候,Python 把引发的实例与该异常一起发送。如果一个 try 包含了一个名为 except name as X:
的子句,变量 X 将会分配给引发中所提供的实例:
try:
pass
except IndexError as X: # 引发的实例对象赋值给 X
pass
包含 as 使得处理器能够访问实例中的数据以及异常类中的方法。
一旦异常在程序中某处由一条 except 子句捕获,它就死掉了,除非由另一个 raise 语句或错误重新引发它。
4.2 利用 raise 传递异常
raise 语句不包括异常名称或额外数据值时,就是重新引发当前异常。如果需要捕捉和处理一个异常,又不希望异常在程序代码中死掉,一般就会使用这种形式:
try:
raise IndexError('spam')
except IndexError:
print('propagating')
raise
propagating
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-4-30f65ef43030> in <module>()
1 try:
----> 2 raise IndexError('spam')
3 except IndexError:
4 print('propagating')
5 raise
IndexError: spam
通过这种方式执行 raise 时,会重新引发异常,并将其传递给更高层的处理器。
4.3 Python 3.X 异常链:raise from
Python 3.X 允许 raise 语句拥有一个可选的 from 子句:
raise exception from otherexception
当使用 from 的时候,第二个表达式指定了另一个异常类或实例,它会附加到引发异常的 __cause__
属性。如果引发的异常没有捕获,Python 把异常也作为标准出错消息的一部分打印出来:
try:
1 / 0
except Exception as E:
raise TypeError('bad!') from E
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-5-a11a717782c4> in <module>()
1 try:
----> 2 1 / 0
3 except Exception as E:
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
TypeError Traceback (most recent call last)
<ipython-input-5-a11a717782c4> in <module>()
2 1 / 0
3 except Exception as E:
----> 4 raise TypeError('bad!') from E
TypeError: bad!
5. assert 语句
assert 语句是 raise 常见使用模式的语法简写,assert 可视为条件式的 raise 语句:
assert <test>, <data>
如果 test 计算为假,Python 就会引发异常:data 项(如果提供了的话)是异常的额外数据。
5.1 例子:收集约束条件(但不是错误)
assert 语句通常用于验证开发期间程序的状况。显示时,其出错消息正文会自动包括源代码的行信息,以及列在 assert 语句中的值:
def f(x):
assert x < 0, 'x must be negative'
return x ** 2
f(1)
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-6-c4fb041f2e31> in <module>()
3 return x ** 2
4
----> 5 f(1)
<ipython-input-6-c4fb041f2e31> in f(x)
1 def f(x):
----> 2 assert x < 0, 'x must be negative'
3 return x ** 2
4
5 f(1)
AssertionError: x must be negative
assert 几乎都是用来收集用户定义的约束条件,而不是捕捉内在的程序设计错误。因为 Python 会自行收集程序的设计错误,通常来说,没必要写 assert 去捕捉超出索引值、类型不匹配以及除数为零之类的事情。
def reciprocal(x):
assert x != 0 # 没用的 assert
return 1 / x # 自动检测 zero
reciprocal(0)
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-7-f01ca2928417> in <module>()
3 return 1 / x
4
----> 5 reciprocal(0)
<ipython-input-7-f01ca2928417> in reciprocal(x)
1 def reciprocal(x):
----> 2 assert x != 0
3 return 1 / x
4
5 reciprocal(0)
AssertionError:
6. with/as 环境管理器
with/as 语句是作为常见 try/finally 用法模式的替代方案。with 语句支持更丰富的基于对象的协议,可为代码块定义支持进入和离开的动作。
6.1 基本使用
with expression [as variable]:
with-block
expression 要返回一个对象,此对象也可返回一个值,赋值给变量名 variable。
6.2 环境管理协议
with 语句实际工作方式:
- 计算表达式,所得到的对象称为环境管理器,它必须有
__enter__
和__exit__
方法。 - 环境管理器的
__enter__
方法会被调用。如果 as 子句存在,其返回值会赋值给 As 子句中的变量,否则,直接丢弃。 - 代码块中嵌套的代码会执行。
- 如果 with 代码块引发异常,
__exit__
(type, value, traceback) 方法就会被调用。这些也是由 sys.exc_info 返回的相同值。如果此方法返回值为假,则异常会重新引发。否则,异常会终止。正常情况下异常是应该被重新引发,这样的话才能传递到 with 语句之外。 - 如果 with 代码块没有引发异常,
__exit__
方法依然会被调用,其 type、value 以及 traceback 参数都会以 None 传递。
6.3 多环境管理器
with 语句可以使用新的逗号语法指定多个环境管理器:
with open('data') as fin, open('res', 'w') as fout:
for line in fin:
if 'some key' in line:
fout.write(line)