到现在为止错误消息还没有被提及,但是如果你尝试过之前的代码应该已经见过一些。有2种(至少)可以分辨的错误类型:语法错误和异常
8.1 语法错误
语法错误,同样称为解析错误,可能是在你学习Python中最常见的一种抱怨了:
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
解析器重复那个讨厌的行并且显示了一个小的‘箭头’在检测到错误的最开始的点上。错误是由箭头之前的记号引起的(或者检测到):在上例中,错误在print()函数中检测到,因为之前少了一个冒号(':')。文件名和行数被打印出来,这样你能知道在哪里可以看到这个情况。
8.2 异常
即使一个语句或者表达式在语法上是正确的,当尝试执行它时也可能引起一个错误。在运行时检测到的错误称为异常并且不是无条件致命的:稍后你将学会如何在Python程序中处理他们。大部分异常不能被程序如理,然而,错误消息中的结果如下:
>>> 10 * (1/0) Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: division 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: Can't convert 'int' object to str implicitly
错误消息的最后一行表明发生的事。异常有不同的类型,并且这个类型也被作为消息的一部分打印出来:在上例中的类型有:ZeroDivisionError,NameError和TypeError。作为异常类型打印出来的字符串是发生的内置异常的名称。对所有的内置异常来说都是正确的,但是对用户定义的异常来说不是(尽管是一个有用的约束)。标准异常的名称是内置的标识符(不是保留关键字)。
其余的行提供基于异常类型的细节和引起的原因。
错误消息的前面部分表明异常发生位置的内容,以堆栈回溯的形式。通常它包含一个列出源行的堆栈回溯;然而,它不会显示从标准输入读取的行。
内置异常列出了内置的异常以及他们的意思。
8.3 处理异常
编写处理异常的程序是可行的。看下面的例子,要求用户输入直到一个有效的整数进入,但是允许用户打断程序(使用Ctrl+C或其他系统支持的方式);注意用户生成的打断将会抛出KeyBoardInterrupt异常。
>>> while True:
... try:
... x = int(input("Please enter a number: "))
... break
... except ValueError:
... print("Oops! That was no valid number. Try again...")
...
try语句效果如下:
- 首先,try子句(在try和except之间的语句)被执行。
- 如果没有异常,except子句将会跳过,try语句完成。
- 如果执行try子句时出现了异常,其余的子句将会被跳过。如果它的类型匹配except关键字后面命名的异常,except子句就会被执行,并且在try语句之后执行。
- 如果一个异常出现但是不匹配except子句的名称,就会跳出try语句;如果没有处理被找到,它就是一个未处理的异常并且异常会停止并显示一个如上的消息。
... except (RuntimeError, TypeError, NameError):
... pass
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # the exception instance
... print(inst.args) # arguments stored in .args
... print(inst) # __str__ allows args to be printed directly,
... # but may be overridden in exception subclasses
... x, y = inst.args # unpack args
... print('x =', x)
... print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError as err:
... print('Handling run-time error:', err)
...
Handling run-time error: division by zero
8.4 抛出异常
raise语句允许程序员强制抛出一个指定的异常。例如:
>>> raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: HiThere
raise唯一的参数表明了抛出的异常。这必须是一个异常实例或者一个异常类(派生自Exception)。如果传递的是一个异常类,它将暗中调用无参的构造函数来初始化:
raise ValueError # shorthand for 'raise ValueError()'
如果你决定无论一个异常被抛出但是不想处理它,一个简单的raise语句形式能允许你重新抛出这个异常:
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: HiThere
8.5 用户定义异常
程序可以命名他们自己的异常通过创建一个新的异常类。异常通常应该从异常类派生出来,不管是直接还是间接。
异常类能被定义成左其他类能做的任何事,但通常要简洁,通常只提供一些属性,这些属性允许关于错误的信息被异常的处理器提取。当创建一个能抛出几种不同错误的模块时,一个常用的做法是为那个模块定义的异常创建一个基类,和为不同错误条件创建指定异常类的子类:
class Error(Exception):
"""Base class for exceptions in this module."""
pass
class InputError(Error):
"""Exception raised for errors in the input.
Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""
def __init__(self, expression, message):
self.expression = expression
self.message = message
class TransitionError(Error):
"""Raised when an operation attempts a state transition that's not
allowed.
Attributes:
previous -- state at beginning of transition
next -- attempted new state
message -- explanation of why the specific transition is not allowed
"""
def __init__(self, previous, next, message):
self.previous = previous
self.next = next
self.message = message
大多数的异常定义的名字结尾都是‘Error’,和标准异常的命名相似。
许多标准模块定义他们自己的异常来报告可能存在于他们定义的函数中的错误。
8.6 定义清理行为
try语句有另一个可选子句,这个字句意图顶一个在所有情况下都必须执行的清理行为。例如:
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
123
finally字句总是在离开try语句之前执行,不管是否有异常出现。当一个异常出现在try子句中并且没有被except子句处理(或者它出现在一个except或else子句),当finally字句执行后他将重新抛出。finally字句同样执行当任意其他的try语句的字句通过break,continue,return语句退出时。一个更复杂的例子:
>>> 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.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
正如你看到的,finally字句在任何情况下都会执行。由除2个字符串抛出的TypeError没有被except字句处理因此在finally字句执行后重新抛出。
在真实应用中,finally子句对于释放额外的资源(例如文件或者网络连接)很有帮助,不管资源是否被顺利的使用。
8.7 预定义清理行为
一些对象定义需要定义的标准清理行为当对象不再需要的时候,不管是否操作成功或者失败的使用了对象。看下面的例子,尝试打开一个文件并且将它的内容输出到屏幕上:
for line in open("myfile.txt"):
print(line, end="")
这段代码的问题是,在代码执行完成后,它会在不确定的时间打开文件。在简单的脚本中这并不是一个问题,但在更大的应用中会。with语句允许对象例如文件以一种确保它们总是适当和正确的被清理的方式来使用。
with open("myfile.txt") as f:
for line in f:
print(line, end="")
当语句执行后,文件f总是会被关闭,即使处理行的时候遇到问题。像文件的对象,提供预定义清理行为将会在他们的文档中表明。