Python 异常处理

PythonBeginner
立即练习

简介

在计算机科学和程序开发中,异常处理是响应异常(即需要特殊处理的异常或非正常情况)发生的过程。Python 拥有许多内置异常,当程序遇到错误时会触发这些异常。此外,许多外部库也包含它们自己的自定义异常,你同样需要对其进行处理。

在本实验中,你将学习 Python 异常处理的基础知识。你将从基础的 try 和 except 代码块开始,逐步学习如何处理多种异常、理解 finally 代码块的用途,最后创建并管理你自己的自定义异常。

基础异常处理

在这一步中,你将学习如何使用 try 和 except 代码块进行 Python 异常处理的基础知识。

数学上有一个公理:不能除以零。如果你在 Python 中尝试这样做,解释器会抛出一个内置的 ZeroDivisionError。让我们看看实际情况。

注意: 如果你不熟悉如何使用 WebIDE(类似 VS Code),请先通过此实验进行学习。

首先,从左侧的文件浏览器中打开 basic_handler.py 文件并添加以下代码。这段代码定义了一个简单的除法函数。

def divide(dividend , divisor):
    print(dividend / divisor)

divide(dividend=10, divisor=5)

现在,在终端中运行该脚本:

python3 basic_handler.py

你将看到正确的输出:

2.0

接下来,让我们看看尝试除以零时会发生什么。修改 basic_handler.py 中的最后一行,将除数改为 0 来调用 divide 函数:

def divide(dividend , divisor):
    print(dividend / divisor)

divide(dividend=10, divisor=0)

再次运行脚本:

python3 basic_handler.py

这一次,程序崩溃并打印出回溯信息(traceback),指出了 ZeroDivisionError:

Traceback (most recent call last):
  File "/home/labex/project/basic_handler.py", line 4, in <module>
    divide(dividend=10, divisor=0)
  File "/home/labex/project/basic_handler.py", line 2, in divide
    print(dividend / divisor)
ZeroDivisionError: division by zero

为了防止程序崩溃并显示用户友好的提示信息,我们可以使用 try 和 except 来「处理」异常。可能导致错误的代码放在 try 代码块中,而如果错误发生时需要执行的代码则放在 except 代码块中。

更新 basic_handler.py 文件,使用以下代码优雅地处理 ZeroDivisionError:

## try-except: 优雅地处理异常
def divide(dividend , divisor):
    try:  ## 尝试执行此代码
        print(dividend / divisor)
    except ZeroDivisionError:  ## 捕获特定类型的异常
        print('You can not divide by 0')

divide(dividend=10, divisor=0)

现在,再次运行脚本:

python3 basic_handler.py

程序不再崩溃,而是打印出了你定义的友好提示信息,并且程序执行正常结束:

You can not divide by 0

在一个异常块中处理多种异常

在这一步中,你将了解如何在一个 except 代码块中处理多种不同的异常类型。当可能发生多种类型的错误时,这对于简化错误处理代码非常有用。

你可以捕获一个异常元组,例如 (ZeroDivisionError, TypeError),从而用相同的逻辑处理它们。

打开 multiple_handler.py 文件并添加以下代码。该函数旨在触发 TypeError(通过尝试连接字符串和整数)或 ZeroDivisionError。

## 在一个 except 块中处理多种异常
def divide(dividend , divisor):
    try:
        if (dividend == 10):
          var = 'str' + 1  ## 这将引发 TypeError
        else:
          print(dividend / divisor)
    except (ZeroDivisionError, TypeError) as error:  ## 捕获多种异常类型
        print(error)  ## 打印错误信息

divide(dividend=20, divisor=5)

首先,让我们在没有异常发生的情况下运行代码。执行脚本:

python3 multiple_handler.py

输出将是除法的结果:

4.0

现在,让我们触发 TypeError。修改 multiple_handler.py 的最后一行,使用 10 作为被除数:

## ... (其余代码) ...
divide(dividend=10, divisor=5)

再次运行脚本:

python3 multiple_handler.py

except 代码块捕获了 TypeError 并打印了相关的错误信息:

can only concatenate str (not "int") to str

最后,让我们触发 ZeroDivisionError。再次修改最后一行,这次尝试除以零。请注意,此时 TypeError 的条件 (dividend == 10) 不满足。

## ... (其余代码) ...
divide(dividend=20, divisor=0)

运行脚本:

python3 multiple_handler.py

同一个 except 代码块现在捕获了 ZeroDivisionError 并打印了它的信息:

division by zero

为了通过验证,请确保脚本中的最后一次调用是 divide(dividend=10, divisor=5) 以触发 TypeError。

异常处理中的 Finally 代码块

在这一步中,你将了解 finally 代码块的目的和用法。finally 子句中的代码无论是否发生或捕获了异常,都会被执行。这使得它非常适合进行清理操作,例如关闭文件或网络连接。

让我们看看它是如何工作的。打开 finally_handler.py 文件并添加以下代码:

## finally 块:无论是否发生异常,始终执行
def divide(dividend , divisor):
    try:
        print(dividend / divisor)
    except ZeroDivisionError:
        print('You can not divide by 0')
    finally:  ## 始终执行,即使发生异常
        print('Execution finished')

divide(dividend=10, divisor=5)

在终端中运行脚本:

python3 finally_handler.py

在这种情况下,没有发生异常。try 代码块成功完成,然后执行 finally 代码块。

2.0
Execution finished

现在,让我们引入一个错误。修改 finally_handler.py 中的最后一行,尝试除以零:

## ... (其余代码) ...
divide(dividend=10, divisor=0)

再次运行脚本:

python3 finally_handler.py

这一次,抛出了 ZeroDivisionError 并被 except 代码块捕获。即使发生了异常,finally 代码块仍然会在 except 代码块执行完毕后运行。

You can not divide by 0
Execution finished

正如你所见,「Execution finished」消息在两种情况下都被打印了出来,这证明了 finally 代码块是放置必须始终运行的代码的可靠位置。

自定义异常

在这一步中,你将学习如何创建自己的自定义异常。这是一个强大的功能,可以使你的应用程序更具可读性和可维护性,因为你可以创建描述程序领域特有问题的特定错误类型。

自定义异常是通过创建一个继承自 Python 内置 Exception 类的新类来定义的。然后,你可以使用 raise 关键字触发此异常。

打开 custom_exception.py 文件并添加以下代码来定义并抛出一个简单的自定义异常:

## 自定义异常:通过继承 Exception 类创建
class MyCustomException(Exception):
    pass

raise MyCustomException  ## 抛出自定义异常

如果你运行此脚本,它将立即停止并显示新异常的回溯信息:

python3 custom_exception.py
Traceback (most recent call last):
  File "/home/labex/project/custom_exception.py", line 5, in <module>
    raise MyCustomException  ## 抛出自定义异常
__main__.MyCustomException

你也可以在抛出自定义异常时提供描述性消息。修改 custom_exception.py,向异常传递一个字符串:

class MyCustomException(Exception):
    pass

raise MyCustomException('A custom message for my custom exception')

运行此代码将在回溯信息中显示你的消息:

Traceback (most recent call last):
  File "/home/labex/project/custom_exception.py", line 4, in <module>
    raise MyCustomException('A custom message for my custom exception')
__main__.MyCustomException: A custom message for my custom exception

处理自定义异常的方法与处理内置异常相同:使用 try...except 代码块。将 custom_exception.py 更新为最终形式,即抛出并处理该自定义异常:

class MyCustomException(Exception):
    pass

try:
    raise MyCustomException('A custom message for my custom exception')
except MyCustomException:
    print('My custom exception was raised')

现在,最后一次运行脚本:

python3 custom_exception.py

程序不再崩溃。相反,它执行了 except 代码块中的代码并打印了你的确认消息:

My custom exception was raised

总结

在本实验中,你学习了 Python 异常处理的基本技术。你从基础的 try 和 except 代码块开始,学会了如何捕获特定错误并防止程序崩溃。接着,你学习了如何在一个 except 代码块中高效地处理多种异常类型。你还探索了 finally 代码块,它对于必须始终运行的清理代码至关重要。最后,你掌握了创建、抛出和处理自定义异常的能力,从而能够在应用程序中构建更健壮、更具描述性的错误处理逻辑。