18·模块五:错误处理与调试入门

异常处理(try-except-finally)

exceptionserror-handlingtry-except

第 18 课 - 异常处理(try-except-finally)

1. 学习目标

完成本课学习后,你将能够:

  1. 理解程序中异常(错误)产生的原因和影响。
  2. 熟练使用 try-except 语句块来捕获和处理特定类型的异常。
  3. 掌握 finally 子句的作用,并理解其与 try-except 的协同工作流程。
  4. 学会使用 else 子句,明确区分正常执行与异常处理的代码路径。
  5. 编写能够优雅应对运行时错误、更加健壮可靠的 Python 程序。

2. 核心概念

什么是异常?

在编写和运行程序时,错误不可避免。Python 将程序运行时发生的错误称为 异常。例如,尝试打开一个不存在的文件、将一个字符串与一个数字相加、或者除以零,都会引发异常。如果异常未被处理,程序通常会崩溃并停止执行。

异常处理:try-except

为了让程序在遇到错误时不会直接"罢工",我们需要 异常处理。基本思路是:把可能出错的代码放在 try(尝试)块里,然后用 except(除了)块来捕获并处理可能发生的错误。

这就像给你的程序上了个"保险"。try 里的代码正常执行;如果中途出了岔子(抛出异常),程序会立刻跳转到对应的 except 块执行补救措施,而不是直接退出。

资源清理:finally

有时候,无论程序执行成功还是失败,我们都必须执行一些"清理"动作,比如关闭文件、断开网络连接或释放锁。finally(最终)子句就是为此而生。无论 try 块中是否发生异常,finally 子句中的代码块都会被执行。

正常路径:else

有时,我们希望在 try 块中的代码 没有 引发任何异常时,才执行某段代码。else(否则)子句正是为了这个目的。else 子句只有在 try 块成功执行完毕后才会运行。

完整流程

  1. try 块中的语句被执行。
  2. 如果发生异常,跳转到匹配的 except 块。
  3. 如果没有发生异常,执行 else 块(如果存在)。
  4. 无论是否发生异常,最后都执行 finally 块(如果存在)。

3. 代码示例

以下示例演示了如何结合 try-except-else-finally 处理一个常见的文件读取操作。我们尝试读取一个文件,并处理可能出现的文件不存在错误。

# 异常处理示例:安全地读取文件

def read_file_safely(filename):
    """
    尝试安全地读取指定文件内容。
    处理文件不存在(FileNotFoundError)的情况。
    """
    file_handle = None  # 初始化文件句柄
    try:
        # 尝试打开文件
        print(f"尝试打开文件: {filename}")
        file_handle = open(filename, 'r') # 这行可能引发 FileNotFoundError
        content = file_handle.read()
        print(f"文件读取成功!")
    except FileNotFoundError as e:
        # 专门捕获文件不存在的错误
        print(f"错误:找不到文件 '{filename}'!")
        print(f"详细信息: {e}")
        content = None  # 可以选择返回一个默认值
    except Exception as e:
        # 捕获其他未知的错误 (使用宽泛的Exception需谨慎,这里仅作演示)
        print(f"发生了一个未预料的错误: {e}")
        content = None
    else:
        # 只有在 try 块中的 open 和 read 都成功时,才会执行这里
        print(f"成功读取了 {len(content)} 个字符。")
        return content  # 返回读取到的内容
    finally:
        # 无论前面发生了什么,这里总会执行
        # 确保文件被正确关闭,避免资源泄露
        if file_handle:
            print("在 finally 中关闭文件...")
            file_handle.close()
        else:
            print("finally 执行:没有打开的文件需要关闭。")
        print("清理工作完成。\n")

# 测试1:尝试读取一个不存在的文件
print("--- 测试 1: 文件不存在 ---")
result = read_file_safely("non_existent_file.txt")
print(f"函数返回值: {result}")

# 测试2:读取一个存在的文件 (你需要先创建这个文件)
# 创建一个测试文件
with open("test_file.txt", "w") as f:
    f.write("Hello, Exception Handling!\nThis is a test file.")

print("--- 测试 2: 文件存在 ---")
result = read_file_safely("test_file.txt")
print(f"函数返回值: {result}")

运行结果预测

  • 测试 1:会触发 FileNotFoundError,执行对应的 except 块和 finally 块。
  • 测试 2:如果文件存在,将成功执行 try 块和 else 块,最后执行 finally 块。

4. 实践练习

练习 1:基础除法运算

编写一个函数 safe_divide(a, b),它接受两个参数 ab,尝试计算并返回 a / b。需要处理两种可能的错误:

  1. b0 导致的 ZeroDivisionError
  2. 参数类型不是数字导致的 TypeError。 在发生错误时,返回一个明确的字符串(如 "除数不能为零" 或 "参数类型错误")。

练习 2:用户输入验证

编写一个程序,要求用户输入一个数字。程序需要捕获用户输入非数字字符串(如 "abc")时引发的 ValueError。使用循环,直到用户输入一个有效的数字为止,并最终打印该数字的平方。

预期输出示例

请输入一个数字: hello
输入错误,请输入一个有效的数字。
请输入一个数字: 12.5
你输入的数字是 12.5,它的平方是 156.25

练习 3:组合练习 - 读取配置并计算

编写一个函数 process_config(filename)。它尝试从指定的文件中读取两行,第一行是数字 x,第二行是数字 y。函数需要:

  1. 处理文件不存在的错误。
  2. 处理文件内容格式错误(某一行不是有效数字)的错误。
  3. 处理除数为零的错误。
  4. 如果一切正常,计算并返回 x / y 的结果。

5. 常见错误

  1. "吞掉"异常:在 except 块中只写一个 pass 或打印一条非常模糊的消息(如 "出错了"),而不记录任何具体信息。这会使调试变得极其困难。
    try:
        risky_operation()
    except:
        pass  # 非常糟糕的做法!忽略了所有错误。
    
  2. 捕获过于宽泛的异常:直接使用 except Exceptionexcept:(捕获所有)可能会掩盖真正的 bug。应优先捕获具体的、可预见的异常类型。
  3. 忽略资源清理:忘记在 finally 中关闭文件或释放网络连接等,可能导致资源泄露和程序异常。
  4. try 块外使用 exceptexcept 必须紧跟在 try 块之后,不能单独存在。
  5. 混淆 elsefinally 的时机else 仅在没有异常时执行,finally 无论如何都会执行。

6. 小结

  • 异常处理机制try-except-finally 结构是编写健壮 Python 程序的核心。它让程序能够优雅地处理意外情况,而不是直接崩溃。
  • try:放置可能引发异常的代码。
  • except:捕获并处理特定类型的异常。可以写多个 except 块来处理不同类型的错误。
  • else:当 try 块没有发生任何异常时执行的代码。用于清晰地分离"正常逻辑"。
  • finally:无论是否发生异常,总会执行的代码。主要用于释放资源(如关闭文件、网络连接)。
  • 最佳实践:尽量捕获具体的异常类型,并为用户提供有意义的错误信息,而不是简单地忽略错误。养成使用 finally 进行资源清理的好习惯。

下一课,我们将学习如何定义自己的异常类型(自定义异常)以及如何链接异常,这能让你的错误处理代码更具表现力和可维护性。

练习编辑器

python
Loading...

继续学习

完成本课后,建议继续学习下一课「自定义异常与异常链」 以巩固所学知识。