- 开胃菜
- 使用 Python 解释器
- 非正式介绍Python
- 深入流程控制
- 数据结构
- 模块
- 输入和输出
- 错误和异常
- 类
- 标准库的简明介绍
- 标准库的简明介绍(第二部分)
- 现在干什么?
- 交互式输入编辑及历史替代
- 浮点算术: 问题和限制
到现在为止, 没有更多的提及错误信息, 但是当你在尝试这些例子时, 或多或少会碰到一些. 这里 (至少) 有两种可以分辨的错误: 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
每个错误信息的最后一行或说明发生了什么. 异常会有很多的类型, 而这个类型会作为消息的一部分打印出来: 在此处的例子中的类型有 ZeroDivisionError
, NameError
和 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
时也会被执行, 像是使用 break
, continue
或者 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 就会被关闭, 就算是在读取时碰到了问题. 像文件这样的对象, 总会提供预定义的清理工作, 更多的可以参考它们的文档.