错误和异常

Learn from python2.7 official documentation

学习至此,曾遇到过的错误信息(error messages)有两种:语法错误(syntax errors)和异常(exceptions)。

8.1 语法错误Syntax Error

语法错误又称为解析错误(parsing errors),语句或表达式在语法上出错时,会引发这种错误。

在错误信息中,出现输入错误的脚本文件名和错误位置的行号将被打印出来,并提示错误信息:SyntaxError: invalid syntax

8.2 异常Exception

即使语法正确,仍在执行时检测到的错误,被称为异常。

大多数的异常并不会被程序处理,而是显示错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined

>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects

异常的错误信息前部分,同样会提示出现输入错误的脚本文件名和错误位置的行号,这部分通常以出现异常源代码行的堆栈回溯清单(stack traceback listing)的形式,显示出发生异常时的上下文。

错误信息的最后一行,会打印出异常类型的名称和此异常的详细描述信息。作为异常类型打印的字符串是发生的内置异常(built-in exception)的名称,对于用户定义异常(user-defined exception)则不一定如此,标准的异常类型是内置的标识符(built-in identifier)而不是保留的关键字(reserved keyword)。

8.3 处理Handling异常

8.3.1 try语句

1
2
3
4
5
6
while True:
try:
x = int(raw_input("Please enter a number: "))
break
except ValueError:
print "Oops! That was no valid number. Try again..."

try语句工作原理:

  • 首先,执行try子句(tryexcept关键字之间的多行语句);

  • 如果没有出现异常,跳过except子句并完成try语句的执行;

  • 如果出现异常,则跳过try子句中剩下的部分,如果异常的类型和except关键字后面的异常类型匹配,则执行except子句,然后完成try语句的执行,继续之后的代码;

  • 如果异常类型和except子句中指定的不匹配,则将传递到外部的try语句中处理(我理解为嵌套),如果没有找到处理器,则将其判定为未处理异常(unhandled exception),并终止执行,返回异常信息。

8.3.2 except子句

一个try语句可以有多个except子句,用于为try子句中可能发生的不同异常分别指定处理程序,每次执行至多执行一个处理程序。

except子句可以将多个异常命名为带括号的元组:

1
2
except (RuntimeError, TypeError, NameError):
pass

最后的except子句可以省略异常名,以用作通配符(wildcard)。此方法可以用于打印错误消息,然后重新引发异常(使用raise语句,令程序自行引发异常):

1
2
3
except:
print "Unexpected error:", sys.exc_info()[0]
raise

8.3.3 else子句

try语句中,可以选择在所有的except子句后面,添加else子句,来写入try子句不引发异常时必须执行的代码:

1
2
3
4
5
6
7
8
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print 'cannot open', arg
else:
print arg, 'has', len(f.readlines()), 'lines'
f.close()

8.3.4 finally子句:定义清理操作Clean-up Action

try语句中,可以在所有的子句后面加入finally子句,finally子句一定会在try语句退出前执行,无论try子句中是否触发异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def divide(x, y):
try:
result = x / y
except ZeroDivisionError:
print "division by zero!"
else:
print "result is", result
finally:
print "executing finally clause"

divide(2, 1)
>>> result is 2
>>> executing finally clause

divide(2, 0)
>>> division by zero!
>>> executing finally clause

divide("2", "1")
>>>
executing finally clause
Traceback (most recent call last):
File "stdy15.py", line 15, in <module>
divide("2", "1")
File "stdy15.py", line 3, in divide
result = x / y
TypeError: unsupported operand type(s) for /: 'str' and 'str'

在实际应用程序中,finally子句用于定义必须在所有情况下执行的清理操作,尤其对于释放外部资源(如文件、网络连接)十分有用。

8.3.5 异常参数exception’s argument

当一个异常发生时,它所具有的相关信息(associated value)称为异常参数,参数的存在与否及其类型,取决于异常类型。异常参数一般作为未处理异常中错误信息的最后一部分打印出来。

except子句可以在异常名后,使用as关键字来指定一个变量,该变量与包含instance.args中存储的异常参数的异常实例(exception instance)绑定,由于异常实例定义了__str()__,所以异常参数可以通过此变量直接打印,而不必引用.args

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try:
raise Exception('spam', 'eggs')
except Exception as inst:
print type(inst) # 异常实例类型
print inst.args
print inst
x, y = inst.args
print 'x =', x
print 'y =', y
>>> <type 'exceptions.Exception'>
>>> ('spam', 'eggs')
>>> ('spam', 'eggs')
>>> x = spam
>>> y = eggs

8.4 抛出Raising异常

raise语句可以用来强制引发指定的异常:

1
2
3
4
5
raise NameError('HiThere')
>>>
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: HiThere

raise语句的唯一参数表示要引发的异常,这个参数必须是一个异常实例或异常类(由Exception派生的类)。

8.5 用户自定义User-defined异常

可以通过创建一个新的异常类,来为自定义异常命名,异常类必须直接或间接地继承Exception类:

1
2
3
4
5
class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)

在这个例子中,Exception默认的__init()__方法被重写,新的方法只是简单的创建类中的一个value属性,这将替代原方法中创建args属性的默认行为:

1
2
3
4
5
6
7
8
9
10
11
12
try:
raise MyError(2 * 2)
except MyError as e:
print 'My exception occurred, value:', e.value
>>> My exception occurred, value: 4

raise MyError('oops!')
>>>
Traceback (most recent call last):
File "stdy15.py", line 1, in <module>
raise MyError('oops!')
__main__.MyError: 'oops!'

大多数异常都定义为名称以"Error"结尾,类似于标准异常的命名。

8.7 with语句&预定义的Predefined清理操作

某些对象定义了在不再需要该对象时要执行的标准清理操作,无论使用该对象的操作是成功还是失败。with语句允许这样的对象总是保证被即使清理:

1
2
3
with open("myfile.txt") as f:
for line in f:
print line,

在执行语句之后,File对象f总是关闭的,即使在执行过程中遇到了问题。