>>> from env_helper import info; info()
页面更新时间: 2024-01-13 09:59:54
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

3.6. Python中的异常处理

到目前为止,在 Python 程序中遇到错误,或“异常”,意味着整个程序崩溃。 你不希望这发生在真实世界的程序中。 相反,你希望程序能检测错误,处理它们,然后继续运行。

3.6.1. 运行时的错误程序

例如,考虑下面的程序,它有一个“除数为零”的错误。

>>> def spam(divideBy):
>>>     return 42 / divideBy
>>>
>>> print(spam(2))
>>> print(spam(12))
>>> print(spam(1))
21.0
3.5
42.0

我们已经定义了名为 spam 的函数,给了它一个变量,然后打印出该函数带各种参数的值。

看看参数是 0 会发生什么情况,下面是运行的输出:

>>> print(spam(0))
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

Cell In [14], line 1
----> 1 print(spam(0))


Cell In [13], line 2, in spam(divideBy)
      1 def spam(divideBy):
----> 2     return 42 / divideBy


ZeroDivisionError: division by zero

当试图用一个数除以零时,就会发生 ZeroDivisionError 。 根据错误信息中给出的行号, 我们知道 spam() 中的 return 语句导致了一个错误。

一旦出现这样的错误,程序就会中断。避免程序在有错误的情况下继续运行是一种保护机制,以防及时发出通过并解决问题。

3.6.2. 函数作为“黑盒”

通常,对于一个函数,你要知道的就是它的输入值(变量)和输出值。 你并非总是需要加重自己的负担,弄清楚函数的代码实际是怎样工作的。 如果以这种高层的方式来思考函数, 通常大家会说,你将该函数看成是一个黑盒。 这个思想是现代编程的基础。

很多模块其中的函数是由其他人编写的。 尽管你在好奇的时候也可以看一看源代码, 但要使用它们你并不需要知道它们是如何工作的。 另外 ,作为编程的建议实践,鼓励在编写函数时不使用全局变量, 通常也不必担心函数的代码会与程序的其他部分发生交叉影响。

3.6.3. except 语句

在Python语言中,异常由 tryexcept 语句来处理。 那些可能出错的语句被放在 try 子句中。 如果错误发生,程序执行就转到接下来的 except 子句开始处。

可以将前面除数为零的代码放在一个 try 子句中, 让 except 子句包含代码,来处理该错误发生时需要做的事。

>>> def spam(divideBy):
>>>     try:
>>>         return 42 / divideBy
>>>     except ZeroDivisionError:
>>>         print('Error: Invalid argument.')
>>>
>>> print(spam(2))
21.0

现在传递参数 0 给这个定义的函数:

>>> print(spam(0))
Error: Invalid argument.
None

如果在 try 子句中的代码导致一个错误, 程序执行就立即转到 except 子句的代码。

3.6.4. except 语句的执行顺序

请注意,在函数调用中的 try 语句块中,发生的所有错误都会被捕捉。 请考虑以下程序,它的做法不一样,将 spam() 调用放在语句块中:

>>> def spam(divideBy):
>>>     return 42 / divideBy
>>>
>>> try:
>>>     print(spam(12))
>>>     print(spam(0))
>>>     print(spam(1))
>>> except ZeroDivisionError:
>>>     print('Error: Invalid argument.')
3.5
Error: Invalid argument.

在上面的代中 print(spam(1)) 永远不会被执行。 程序一旦执行跳到 except 子句的代码,会继续照常向下执行,不会回到 try 子句。

try 语句按照如下方式工作;

  • 首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)

  • 如果没有异常发生,忽略 except 子句, try 子句执行后结束。

  • 如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。

    • 如果异常的类型和 except 之后的名称相符,那么对应的except 子句将被执行。

    • 执行 try 语句之后的代码。

  • 如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。

一个 try 语句可能包含多个 except 子句,分别来处理不同的特定的异常。最多只有一个分支会被执行。

处理程序将只针对对应的 try 子句中的异常进行处理,而不是其他的 try 的处理程序中的异常。

一个 except 子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组,例如:

except (RuntimeError, TypeError, NameError):
    pass

最后一个 except 子句可以忽略异常的名称,它将被当作通配符使用。你可以使用这种方法打印一个错误信息,然后再次把异常抛出。

>>> 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
OS error: [Errno 2] No such file or directory: 'myfile.txt'

3.6.5. else 子句

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()
cannot open -f
/home/bk/.local/share/jupyter/runtime/kernel-551e78aa-2f53-48c5-84a6-ef4a5f5b306f.json has 12 lines

使用 else 子句比把所有的语句都放在 try 子句里面要好,这样可以避免一些意想不到的、而 except 又没有捕获的异常。

3.6.6. 其他

上面已经看到,异常处理并不仅仅处理那些直接发生在 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