《Python入门教程》8. 错误和异常

  1. 开胃菜
  2. 使用 Python 解释器
  3. 非正式介绍Python
  4. 深入流程控制
  5. 数据结构
  6. 模块
  7. 输入和输出
  8. 错误和异常
  9. 标准库的简明介绍
  10. 标准库的简明介绍(第二部分)
  11. 现在干什么?
  12. 交互式输入编辑及历史替代
  13. 浮点算术: 问题和限制

到现在为止, 没有更多的提及错误信息, 但是当你在尝试这些例子时, 或多或少会碰到一些. 这里 (至少) 有两种可以分辨的错误: syntax error 和 exception , 按中文来说, 就是语法错误和异常.

8.1. 语法错误

语法错误, 也可以认为是解析时错误, 这是在你学习 Python 过程中最有可能碰到的:

>>> whileTrue print(‘Hello world’)

File “<stdin>”, line 1, in ?

while True print(‘Hello world’) ^

SyntaxError: invalid syntax

解析器会重复出错的那行, 然后显示一个小箭头, 指出探测到错误时最早的那个点. 错误一般是由箭头所指的地方导致 (或者至少是此处被探测到): 在这个例子中, 错误是在 print() 函数这里被发现的, 因为在它之前少了一个冒号 (':'). 文件的名称与行号会被打印出来, 以便于你能找到一个脚本中导致错误的地方.

8.2. 异常

尽管语句或表达式语法上是没有问题的, 它同样也会在尝试运行时导致一个错误. 在执行时探测到的错误被成为 exception , 也就是异常, 但它并不是致命的问题: 你将会很快学到如何在 Python 程序中处理它们. 大多数异常并不会被程序处理, 不过, 导致错误的信息会被显示出来:

>>> 10 * (1/0)

Traceback (most recent call last):

File “<stdin>”, line 1, in ?

ZeroDivisionError: int division or modulo by zero

 >>> 4 + spam*3

Traceback (most recent call last):

File “<stdin>”, line 1, in ?

NameError: name ‘spam’ is not defined 

>>> ‘2’ + 2

Traceback (most recent call last):

File “<stdin>”, line 1, in ?

TypeError: Can’t convert ‘int’ object to str implicitly

每个错误信息的最后一行或说明发生了什么. 异常会有很多的类型, 而这个类型会作为消息的一部分打印出来: 在此处的例子中的类型有 ZeroDivisionErrorNameError 和 TypeError. 作为异常类型被输出的字符串其实是发生内建异常的名称. 对于所有内建异常都是那样的, 但是对于用户自定义的异常, 则可能不是这样 (尽管有某些约定). 标准异常的名字是内建的标识符 (但并不是关键字).

改行剩下的部分则提供更详细的信息, 是什么样的异常, 是怎么导致的.

错误消息的前面部分指出了异常发生的上下文, 以 stack traceback (栈追踪) 的方式显示. 一般来说列出了源代码的行数; 但是并不会显示从标准输入得到的行数.

Built-in Exceptions 列出了内建的异常和它们的意义.

8.3. 处理异常

写程序来处理异常是可能的. 看看下面的例子, 它请求用户输入一个合法的整数, 但是也允许用户来中断程序 (使用 Control-C 或任何操作系统支持的); 注意, 用户生成的中断是通过产生异常 KeyboardInterrupt:

>>> whileTrue

… try

 x = int(input(“Please enter a number: “)) 

… break

… except ValueError: 

 print(“Oops! That was no valid number. Try again…”) 

try 语句像下面这样使用.

  • 首先, try clause (在 try 和 except 之间的语句) 将被执行.
  • 如果没有异常发生, except clause 将被跳过, try 语句就算执行完了.
  • 如果在 try 语句执行时, 出现了一个异常, 该语句的剩下部分将被跳过. 然后如果它的类型匹配到了 except 后面的异常名, 那么该异常的语句将被执行, 而执行完后会运行 try 后面的问题.
  • 如果一个异常发生时并没有匹配到 except 语句中的异常名, 那么它就被传到 try 语句外面; 如果没有处理, 那么它就是 unhandled exception 并且将会像前面那样给出一个消息然后执行.

一个 try 语句可以有多于一条的 except 语句, 用以指定不同的异常. 但至多只有一个会被执行. Handler 仅仅处理在相应 try 语句中的异常, 而不是在同一 try 语句中的其他 Handler. 一个异常的语句可以同时包括多个异常名, 但需要用括号括起来, 比如:

… except (RuntimeError, TypeError, NameError):

… pass

最后的异常段可以忽略异常的名字, 用以处理其他的情况. 使用这个时需要特别注意, 因为它很容易屏蔽了程序中的错误! 它也用于输出错误消息, 然后重新产生异常 (让调用者处理该异常):

importsys

try:

f = open(‘myfile.txt’)

s = f.readline()

i = int(s.strip())

 except IOError as err:

print(“I/O 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 语句后面. 它常用于没有产生异常时必须执行的语句. 例如:

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()

使用 else 比额外的添加代码到 try 中要好, 因为这样可以避免偶然的捕获一个异常, 但却不是由于我们保护的代码所抛出的.

当一个异常发生了, 它可能有相关的值, 这也就是所谓的异常的参数. 该参数是否出现及其类型依赖于异常的类型.

在 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

如果一个异常有参数, 它们将作为异常消息的最后一部分打印出来.

异常的 handler 处理的异常, 不仅仅是 try 语句中那些直接的异常, 也可以是在此处调用的函数所产生的异常. 例如:

>>> def this_fails(): 

 x = 1/0 

>>> try:

  this_fails()

 … except ZeroDivisionError as err:

  print(‘Handling run-time error:’, err) 

 

Handling run-time error: int division or modulo by zero

8.4. 抛出异常

raise 语句允许程序员强制一个特定的异常的发生. 举个例子:

>>> raise NameError(‘HiThere’)

Traceback (most recent call last):

File “<stdin>”, line 1, in ?

NameError: HiThere

给 raise 的唯一参数表示产生的异常. 这必须是一个异常实例或类 (派生自 Exception 的类).

如果你需要决定产生一个异常, 但是不准备处理它, 那么一个简单的方式就是, 重新抛出异常:

>>> 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 ?

NameError: HiThere

8.5. 自定义异常

程序中可以通过定义一个新的异常类 (更多的类请参考 ) 来命名它们自己的异常. 异常需要从 Exception 类派生, 既可以是直接也可以是间接. 例如:

>>> classMyError(Exception):

 … def __init__(self, value):

  self.value = value 

… def __str__(self): 

… return repr(self.value)

 

>>> 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 “<stdin>”, line 1, in ?

__main__.MyError: ‘oops!’

在这个例子中, Exception 的默认方法 __init__() 被覆写了. 现在新的异常类可以像其他的类一样做任何的事, 但是常常会保持简单性, 仅仅提供一些可以被 handler 处理的异常信息. 当创建一个模块时, 可能会有多种不同的异常, 一种常用的做法就是, 创建一个基类, 然后派生出各种不同的异常:

大多数异常定义时都会以 “Error” 结尾, 就像标准异常的命名.

大多数标准模块都定义了它们自己的异常, 用于报告在它们定义的函数中发生的错误. 关于更多类的信息请参考 .

8.6. 定义清理动作

try 语句有另一种可选的从句, 用于定义一些扫尾的工作, 此处定义的语句在任何情况下都会被执行. 例如:

>>> try

… raise KeyboardInterrupt 

… finally

 print(‘Goodbye, world!’) 

 Goodbye, world!

 KeyboardInterrupt

 Traceback (most recent call last):

File “<stdin>”, line 2, in ?

一个 *finally 语句*总是在离开 try 语句前被执行, 而无论此处有无异常发生. 当一个异常在 try 中产生, 但是并没有被 except 处理 (或者它发生在 except 或 else 语句中), 那么在 finally 语句执行后会被重新抛出. finally 语句在其他语句要退出 try 时也会被执行, 像是使用 breakcontinue 或者 return. 一个更复杂的例子:

正如你所看到的, finally 语句在任何情况下都被执行了. 由于将两个字符串相除而产生的 TypeError 并没有被 except 语句处理, 因此在执行 finally 后被重新抛出.

在真正的应用中, finally 是非常有用的, 特别是释放额外的资源 (类似文件或网络连接), 无论此资源是否成功使用.

8.7. 预定义的清理动作

有些对象定义了标准的清理工作, 特别是对象不再需要时, 无论对其使用的操作是否成功. 看看下面的例子, 它尝试打开一个文件并输出内容至屏幕.

for line in open(“myfile.txt”):

print(line)

前面这段代码的问题在于, 在此代码成功执行后, 文件依然被打开着. 在简单的脚本中这可能不是什么问题, 但是对于更大的应用来说却是个问题. with 语句就允许像文件这样的对象在使用后会被正常的清理掉.

with open(“myfile.txt”) as f:

 for line in f:

print(line)

在执行该语句后, 文件 f 就会被关闭, 就算是在读取时碰到了问题. 像文件这样的对象, 总会提供预定义的清理工作, 更多的可以参考它们的文档.

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注