到目前为止,错误消息还没有被提及,但如果你尝试过你可能见过的一些例子。 有(至少)两种可区分的错误:语法错误和异常。
语法错误,也称为解析错误parsing errors,可能是您在学习Python时得到的最常见的抱怨:
while True print ('Hello world')
File "", line 1
while True print ('Hello world')
^
SyntaxError: invalid syntax
解析器重复有问题的行并显示一个指向检测到错误的行中最早点的“箭头”。 错误是由箭头前面的标记(或至少在其处检测到)引起的:在此示例中,由于在它之前缺少冒号(’:’),所以在函数print()处检测到错误。 文件名和行号会打印出来,以便在输入来自脚本时知道在哪里查看。
即使语句或表达式在语法上是正确的,但在尝试执行它时可能会导致错误。 执行过程中检测到的错误称为异常,并不是无条件致命的:您将很快学会如何在Python程序中处理它们。 然而,大多数异常不是由程序处理的,并且会导致如下所示的错误消息:
10 * (1/0)
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
in ()
----> 1 10 * (1/0)
ZeroDivisionError: division by zero
4 + spam*3
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in ()
----> 1 4 + spam*3
NameError: name 'spam' is not defined
'2' + 2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 '2' + 2
TypeError: Can't convert 'int' object to str implicitly
错误消息的最后一行表示发生了什么。 异常有不同的类型,并且类型被打印为消息的一部分:示例中的类型是ZeroDivisionError,NameError和TypeError。 作为异常类型打印的字符串是引起的内置异常的名称。 对于所有内置的异常情况都是如此,但对于用户定义的异常情况不必如此(尽管这是一个有用的约定)。 标准异常名称是内置标识符(不是保留字)。
该行的其余部分根据异常的类型以及导致它的原因提供了详细信息。
错误消息的前面部分以堆栈回溯的形式显示发生异常的上下文context。 通常它包含一个栈回溯列表源代码行; 但是,它不会显示从标准输入读取的行。
详细的内置异常和他们的含义可以参考内置异常部分
编写处理选定异常的程序是可能的。 看下面的例子,它会要求用户输入,直到输入一个有效的整数,但允许用户中断程序(使用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语句的工作原理如下。
1. 首先,try子句(try和except关键字之间的语句)被执行。
2. 如果没有发生异常,则跳过except子句并结束try语句的执行。
3. 如果在执行try子句期间发生异常,则跳过子句的其余部分。 然后,如果它的类型匹配以except关键字命名的异常,则会执行except子句,然后在try语句之后继续执行。
4. 如果发生的异常与except子句中指定的异常不匹配,则会传递给外部try语句; 如果没有找到处理程序,则它是一个未处理的异常,并执行停止并显示如上所示的消息。
try语句可能有多个except子句,用于为不同的异常指定处理程序。 最多只有一个处理程序将被执行。 处理程序只处理发生在相应try子句中的异常,而不处理相同try语句的其他处理程序。 except子句可以将多个异常命名为括号化的元组,例如:
excapt (RuntimeError, TypeError, NameError):
pass
如果except子句中的类是相同的类或其基类(但不是反其道而行之 - 列出一个派生类的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子句可以省略exception name(s),作为通配符。 请谨慎使用此功能,因为以这种方式很容易掩盖真正的编程错误! 它也可以用来打印错误消息,然后重新引发异常(允许调用者也处理异常):
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子句,当存在时,它必须遵循除了子句之外的所有子句。 如果try子句不引发异常,则必须执行该代码。 例如:
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语句的代码引发的意外的异常。
当异常发生时,可能有一个关联的值associated value,也称为异常的参数。 参数的存在和类型取决于异常类型。
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)
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
如果一个异常有参数,它们将作为未处理异常消息的最后一部分(’detail’)打印出来。
异常处理程序不仅仅处理发生在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
raise语句允许程序员强制执行指定的异常。 例如
raise NameError('HiThere')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in ()
----> 1 raise NameError('HiThere')
NameError: HiThere
raise的唯一参数表明了要引起的异常。 这必须是异常实例或异常类(从Exception派生的类)。 如果传递一个异常类,它将通过调用它的没有参数的构造函数被隐式实例化:
raise.ValueError # shorthand for 'raise ValueError()'
如果您需要确定是否引发了异常但无意处理异常,则可以使用更简单的raise语句来重新引发异常:
try:
raise NameError('HiThere')
except NameError:
print('An except flew by!')
raise
An except flew by!
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in ()
1 try:
----> 2 raise NameError('HiThere')
3 except NameError:
4 print('An except flew by!')
5 raise
NameError: HiThere
程序可以通过创建一个新的异常类来命名它们自己的异常(更多关于Python类的参见Classes)。 异常通常应直接或间接地从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 pccurred
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”结尾的名字来定义,类似于标准异常的命名
许多标准模块定义它们自己的异常,以报告可能在其定义的功能中发生的错误。 关于类的更多信息在类章节中介绍。
try 语句还有另一个可选的字句,它用来定义在所有情况circumstances下都必须要执行的清理行为
try:
raise KeyboardInterrupt
finally:
print('Goodbye, world')
Goodbye, world
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
in ()
1 try:
----> 2 raise KeyboardInterrupt
3 finally:
4 print('Goodbye, world')
KeyboardInterrupt:
finally子句总是在离开try语句之前执行,无论是否发生异常。 当try子句中发生异常并且没有被except子句处理(或者它发生在except或else子句中)时,它会在finally子句执行后重新引发。 当try语句中有任何通过break,continue或return语句表示的其他子句时,finally子句也会在他们之后(on the way out)执行。 一个更复杂的例子:
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
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 divide('2', '1')
in divide(x, y)
1 def divide(x, y):
2 try:
----> 3 result = x/y
4 except ZeroDivisionError:
5 print('division by zero!')
TypeError: unsupported operand type(s) for /: 'str' and 'str'
正如你所看到的,finally子句在任何情况下都被执行。 通过除两个字符串引发的TypeError不是由except子句处理的,因此在执行了finally子句之后重新引发。
在真实世界的应用程序中,finally子句对于释放外部资源(如文件或网络连接)很有用,无论资源的使用是否成功。
某些对象定义了在不再需要对象时要执行的标准清除操作,而不管使用对象的操作是成功还是失败。 看下面的例子,它试图打开一个文件并将其内容打印到屏幕上。
for line in open('myfile.txt'):
print(line, end=' ')
这段代码的问题在于,在代码的这部分完成执行后,它会使文件保持打开状态的时间不确定。 这在简单脚本中不是问题,但对于较大的应用程序可能是一个问题。 with语句允许像文件这样的对象以确保它们始终被正确清理的方式使用。
with open('myfile.txt') as f:
for line in f:
print(line, end=' ')
语句执行后,即使在处理行时遇到问题,文件f也始终关闭。 与文件一样,提供预定义清理操作的对象将在其文档中指明。