《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: “)) breakexcept 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

最后的异常段可以忽略异常的名字, 用以处理其他的情况. 使用这个时需要特别注意, 因为它很容易屏蔽了程序中的错误! 它也用于输出错误消息, 然后重新产生异常 (让调用者处理该异常):importsystry: 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 处理的异常信息. 当创建一个模块时, 可能会有多种不同的异常, 一种常用的做法就是, 创建一个基类, 然后派生出各种不同的异常:classError(Exception): “””Base class for exceptions in this module.”””passclassInputError(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 classTransitionError(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 ?

一个 *finally 语句*总是在离开 try 语句前被执行, 而无论此处有无异常发生. 当一个异常在 try 中产生, 但是并没有被 except 处理 (或者它发生在 except 或 else 语句中), 那么在 finally 语句执行后会被重新抛出. finally 语句在其他语句要退出 try 时也会被执行, 像是使用 breakcontinue 或者 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 ? File “<stdin>”, line 3, in divide TypeError: unsupported operand type(s) for /: ‘str’ and ‘str’

正如你所看到的, 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 就会被关闭, 就算是在读取时碰到了问题. 像文件这样的对象, 总会提供预定义的清理工作, 更多的可以参考它们的文档.

留下评论

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