对于 Python 初学者来说,经常会看到一些报错信息。Python错误被分为两种:语法错误和异常。语法错误又称解析错误,是学习 Python 时最常见的错误;异常是即使语句或表达式使用了正确的语法,执行时仍可能触发错误。
一、语法错误
Python 的语法错误或者称之为解析错,是初学者经常碰到的,例如:
>>> while True print('Hello world') File "<stdin>", line 1, in ? while True print('Hello world') ^ SyntaxError: invalid syntax
这个例子中,函数 print() 被检查到有错误,是它前面缺少了一个冒号(:)。语法分析器指出了出错的一行,并且在最先找到的错误的位置标记了一个小小的箭头。错误信息还输出文件名与行号,在使用脚本文件时,就可以知道去哪里查错。
二、异常
即便 Python 程序的语法是正确的,在运行它的时候,也有可能发生错误。运行期检测到的错误被称为异常。
大多数的异常都不会被程序处理,都以错误信息的形式展现在这里:
>>> 10 * (1/0) Traceback (most recent call last): File "<stdin>", line 1, in ? ZeroDivisionError: division 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。作为异常类型打印的字符串是发生的内置异常的名称。
错误信息开头用堆栈回溯形式展示发生异常的语境。一般会列出源代码行的堆栈回溯;但不会显示从标准输入读取的行。
三、异常处理
以下例子中,让用户输入一个合法的整数,但是允许用户中断这个程序(使用 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 子句 (try 和 except 关键字之间的(多行)语句);
- 如果没有触发异常,则跳过 except 子句,try 语句执行完毕;
- 如果在执行 try 子句时发生了异常,则跳过该子句中剩下的部分。 如果异常的类型与 except 关键字后指定的异常相匹配,则会执行 except 子句,然后跳到 try/except 代码块之后继续执行;
- 如果发生的异常与 except 子句 中指定的异常不匹配,则它会被传递到外部的 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")
try except 语句还有一个可选的 else 子句,如果使用这个子句,那么必须放在所有的 except 子句之后。这个子句将在 try 子句没有发生任何异常的时候执行。例如:
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又没有捕获的异常。
异常处理并不仅仅处理那些直接发生在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
四、触发异常
Python 使用 raise 语句抛出一个指定的异常,例如:
>>> raise NameError('HiThere') Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: HiThere
raise 唯一的参数就是要触发的异常。这个参数必须是异常实例或异常类(派生自 BaseException 类,例如 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
五、异常链
如果一个未处理的异常发生在 except 部分内,它将会有被处理的异常附加到它上面,并包括在错误信息中:
>>>try: ... open("database.sqlite") ...except OSError: ... raise RuntimeError("unable to handle error") ... Traceback (most recent call last): File "<stdin>", line 2, in <module> FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError: unable to handle error
为了表明一个异常是另一个异常的直接后果, raise 语句允许一个可选的 from 子句:
# exc must be exception instance or None. raise RuntimeError from exc
转换异常时,这种方式很有用。例如:
>>>def func(): ... raise ConnectionError ... >>>try: ... func() ... except ConnectionError as exc: ... raise RuntimeError('Failed to open database') from exc ... Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 2, in func ConnectionError The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError: Failed to open database
它还允许使用 from None 表达禁用自动异常链:
>>>try: ... open('database.sqlite') ... except OSError: ... raise RuntimeError from None ... Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError
六、定义清理操作
try 语句还有另外一个可选的子句,它定义了无论在任何情况下都会执行的清理行为。 例如:
>>> try: ... raise KeyboardInterrupt ...finally: ... print('Goodbye, world!') ... Goodbye, world! Traceback (most recent call last): File "<stdin>", line 2, in <module> KeyboardInterrupt
以上例子不管 try 子句里面有没有发生异常,finally 子句都会执行。
如果存在 finally 子句,则 finally 子句是 try 语句结束前执行的最后一项任务。不论 try 语句是否触发异常,都会执行 finally 子句。以下内容介绍了几种比较复杂的触发异常情景:
- 如果执行 try 子句期间触发了某个异常,则某个 except 子句应处理该异常。如果该异常没有 except 子句处理,在 finally 子句执行后会被重新触发;
- except 或 else 子句执行期间也会触发异常。 同样,该异常会在 finally 子句执行之后被重新触发;
- 如果 finally 子句中包含 break、continue 或 return 等语句,异常将不会被重新引发;
- 如果执行 try 语句时遇到 break,、continue 或 return 语句,则 finally 子句在执行 break、continue 或 return 语句之前执行;
- 如果 finally 子句中包含 return 语句,则返回值来自 finally 子句的某个 return 语句的返回值,而不是来自 try 子句的 return 语句的返回值。
例如:
>>>def bool_return(): ... try: ... return True ... finally: ... return False ... >>>bool_return() False
这是一个比较复杂的例子:
>>>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 子句。except 子句不处理两个字符串相除触发的 TypeError,因此会在 finally 子句执行后被重新触发。
在实际应用程序中,finally 子句对于释放外部资源(例如文件或者网络连接)非常有用,无论是否成功使用资源。