[译]The Python Tutorial#Errors and Exceptions
到现在为止都没有过多介绍错误信息,但是已经在一些示例中使用过错误信息。Python至少有两种类型的错误:语法错误以及异常
8.1 Syntax Errors
语法错误,也称解析错误,是Python初学者经常抱怨的问题。
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
Python解析器重复打印错误行,并且展示一个小箭头,箭头指向错误行中错误最早被侦测到的位置。错误由箭头前面的语句导致(至少侦测到是这样的):以上示例中,错误在print()
函数被侦测到,因为在它之前的冒号:
缺失了。文件名和行号也被打印出来,当程序来自脚本时可以方便定位问题。
8.2 Exceptions
即使语句或者表达式在语法上是正确的,但是在执行的时候也可能会发生错误。在执行期间侦测到的问题叫做异常,异常不是绝对的致命错误:接下来会介绍如何在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
。作为异常类型打印的字符串是built-in异常的名字。对于所有built-in异常来说都是如此,尽管这个约定很有用,但是并不是所有用户自定义异常都会遵守。标准异常名字是built-in标识符(不是保留字)。
剩余行基于异常类型展示了详细的信息以及异常发生的原因。
前面部分错误信息以堆栈回溯的方式,展示异常发生的上下文信息。通常堆栈回溯中列出了源代码行;然而,当程序是从标准输入中读取时,不会展示行。
Built-in异常列举了所有built-in异常以及它们的意义。
8.3 Handling Exceptions
Python程序允许处理指定异常。以下程序要求用户循环输入,直到输入为有效整数停止,但是也可以中断程序(使用Control-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
和except
之间的try子句 - 如果没有异常发生,跳过except子句,
try
语句执行完毕 - 如果try子句执行期间有异常发生,该子句内剩余语句被跳过。接下来如果抛出的异常类型可以与关键字
except
后的异常匹配,那么except子句执行,接着继续执行try
语句后的语句。 - 如果异常发生但是没有匹配到except后的异常,那么异常抛到外层
try
语句;如果异常得不到处理,该异常成为未处理异常,导致程序终止并且像以上示例一样打印信息。
一个try
语句可以有多个except子句,为不同的异常指定处理方法。至多只有一个except子句会被执行。处理程序只会处理发生在对应try子句中的异常,而不会处理发生在同一try
语句中其他处理程序中的异常。一个except子句可以使用带括号的元组列出多个异常,就像这样:
... except (RuntimeError, TypeError, NameError):
... pass
except
子句中的类可以匹配类型是其子类或者它自己类型的异常(但反过来不行,except子句中的子类不会匹配父类异常)。例如以下代码会依次打印B, C, D:
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")
注意以上的except子句若反过来写(except B
在前),那么将会打印B, B, B ——第一个except子句的匹配被触发。
最后一个except子句可以省略异常名字作为通配符。因为这样做会隐藏其他的真实程序错误,所有要谨慎使用。也可以用来打印错误信息并且重新抛出(允许调用者处理异常):
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
try
...except
语句有一个可选的else子句,该子句只能出现在所有except子句之后。若要求try子句没有抛出异常时必须执行一段代码,使用else子句很有用(译注:else
子句会被return, break, continue
跳过)。例如:
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()
使用 else
子句比在 try
子句中附加代码要好,因为这样可以避免 try
... except
意外的捕获的本不属于它们保护的那些代码抛出的异常。
异常发生时,可以携带与之关联的值,也称作异常参数。异常参数是否可以存在以及其类型取决于异常类型。
except子句可以在异常名字后指定变量。该变量绑定到异常实例,异常参数存储在instance.args
属性中。方便起见,异常实例定义了__str__()
以便异常参数可以直接打印,而不是使用.args
引用。也可以首先实例化异常,并在抛出它之前加入任何想要的参数:
>>> 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
对于未处理异常来说,如果它有参数,那么参数将在错误信息的后面部分(详细信息部分)打印。
异常处理程序不仅仅会处理即时发生在try子句中的异常,还会处理try子句中直接或者间接调用的函数中发生的异常。例如:
>>> 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 Raising Exceptions
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 User-defined Exception
程序中可以通过创建新异常类的方式提出自己的异常(参见Classes
获取Python类的更多信息)。异常必须直接或者间接继承自Exception
类。
自定义异常类拥有其他类的功能,但通常需要保持其简洁性,只提供几个供异常处理程序提取错误信息的属性。需要创建一个抛出若干不同错误的模块时,比较好的实践是,为定义在这个模块中的异常创建父类,由子类创建对应不同错误的具体异常:
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"结尾。
许多标准模块都定义了自己的异常,这些异常对应模块中定义的函数中可能发生的错误。更多信息参考Classes。
8.6 Defining Clean-up Actions
try
语句有可选的在任何情况下都会执行的子句,可用于定义清理动作。例如:
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
无论是否有异常发生,finally子句在离开try
语句之前总是会执行。当try
子句中有异常发生并且没有被except
子句处理(或者异常发生在except
子句或else
子句),finally
子句执行之后,这些异常会重新抛出。当try
语句的其他子句通过break, continue
或者return
语句离开时,finally
子句也会执行。以下是较为复杂的示例:
>>> 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
子句在任何情况下都会执行。两个字符串相除产生的TypeError
异常没有被except
子句处理,因此在finally
子句执行完毕之后被重新抛出。
在实践应用中,finally
子句用来释放外部资源(比如文件和网络连接),不论资源的使用是否成功。
8.7 Predefined Clean-up Actions
一些对象定义了标准的清理动作,当不再需要这些对象时,会执行清理动作,而不论使用对象的操作是否成功。以下示例读取文件并打印内容到显示器:
for line in open("myfile.txt"):
print(line, end="")
这段代码的问题是:当代码执行完毕后,文件会保留打开状态一段不确定的时间。这在简单的脚本中不是什么大问题,但是在大型的应用程序中会出问题。with
语句使得像文件一样的对象可以被立即准确回收。
with open("myfile.txt") as f:
for line in f:
print(line, end="")
语句执行后,即使在执行代码时遇到问题,文件f总是会被关闭。像文件一样提供预定义清理动作的对象会在其说明文档中指示这点。