# 第 2-3 课:异常处理 ## 一、本课目标 完成本课后,你将能够: 1. 解释异常是什么,以及为什么需要处理异常; 2. 阅读 Python 异常信息中的异常类型; 3. 使用 `try...except` 处理预期内的错误; 4. 分别处理 `ValueError`、`ZeroDivisionError` 和 `FileNotFoundError`; 5. 理解 `else` 和 `finally` 的执行时机; 6. 避免使用过大的 `try` 范围或空白 `except`; 7. 让文件读取和数据转换失败时给出清楚的中文提示。 ## 二、前置知识 学习本课前,需要掌握: - 条件判断、函数和返回值; - `int()` 类型转换; - 模块与程序入口判断; - 使用 `Path` 读取和写入文本文件。 ## 三、异常是什么 异常(Exception)是程序运行过程中发生的非正常情况。 例如: ```python number = int("五") ``` 运行后会看到类似信息: ```text ValueError: invalid literal for int() with base 10: '五' ``` 中文解释:`int()` 需要可以转换为整数的内容,但字符串 `"五"` 不符合要求。 如果不处理异常,程序会在出错位置停止。异常处理可以让程序识别预期内的问题,给用户明确提示,并决定是否继续运行。 异常处理不是“隐藏所有错误”。我们只处理已经理解并且知道如何应对的异常。 ## 四、使用 `try...except` 基本结构如下: ```python try: number = int(number_text) except ValueError: print("请输入有效整数。") ``` 执行顺序: 1. Python 先执行 `try` 中的代码; 2. 没有异常时,跳过 `except`; 3. 发生 `ValueError` 时,停止执行 `try` 中剩余代码; 4. 转到对应的 `except ValueError`; 5. `except` 执行结束后,程序可以继续向下运行。 ## 五、只捕获明确的异常 本课会遇到三种常见异常。 ### 5.1 `ValueError` 值错误(Value Error,`ValueError`)表示值的类型可能可以使用,但具体内容不符合要求: ```python int("优秀") ``` ### 5.2 `ZeroDivisionError` 除零错误(Zero Division Error,`ZeroDivisionError`)表示程序尝试用数字除以零: ```python average = 180 / 0 ``` ### 5.3 `FileNotFoundError` 文件未找到错误(File Not Found Error,`FileNotFoundError`)表示程序读取的路径不存在: ```python content = file_path.read_text(encoding="utf-8") ``` 不推荐写空白 `except`: ```python try: number = int(number_text) except: print("出错了。") ``` 这种写法会把多种不同问题混在一起,用户不知道发生了什么,开发者也更难找到真正原因。 ## 六、一个 `try` 对应多个 `except` 一段代码可能出现不同异常,可以分别处理: ```python try: content = file_path.read_text(encoding="utf-8") number = int(content.strip()) except FileNotFoundError: print("没有找到文件。") except ValueError: print("文件内容不是有效整数。") ``` Python 会根据实际异常类型进入匹配的分支。清楚区分异常类型,才能给出准确提示。 ## 七、`else` 的作用 `else` 只在 `try` 没有发生异常时执行: ```python try: average = total_score / agent_count except ZeroDivisionError: print("Agent 数量不能为 0。") else: return average ``` 这样可以让 `try` 只包含可能发生异常的操作,成功后的普通逻辑放到 `else`。 ## 八、`finally` 的作用 `finally` 无论有没有发生异常都会执行: ```python try: content = file_path.read_text(encoding="utf-8") except FileNotFoundError: print("没有找到文件。") finally: print("文件读取结束。") ``` 它常用于必须执行的收尾工作。本课用输出语句观察执行时机。上一课使用的 `with` 已经能帮助关闭文件,因此不要为了使用 `finally` 而放弃更清晰的 `with` 或 `Path.read_text()`。 ## 九、异常与条件判断的区别 条件判断适合处理程序能够直接检查的业务规则: ```python if score < 0 or score > 100: print("评分必须在 0 到 100 之间。") ``` 异常处理适合处理操作执行时可能失败的情况: ```python try: score = int(score_text) except ValueError: print("评分必须是整数。") ``` 因此,评分转换失败使用 `except ValueError`,评分超出范围使用 `if`,两者各自负责不同问题。 ## 十、缩小 `try` 的范围 推荐: ```python try: number = int(number_text) except ValueError: return None print("转换完成。") ``` 不要把大量无关代码全部放进 `try`。范围过大时,即使其他代码产生同类型异常,也可能被误认为是预期问题。 ## 十一、完整示例 本课示例文件是: ```text 02_python进阶/2_3_异常处理/exception_example.py ``` 示例依次演示: 1. 使用 `try...except` 处理整数转换失败; 2. 使用 `else` 返回成功的除法结果; 3. 分别处理正常文件和不存在的文件; 4. 使用 `finally` 输出每次读取结束提示。 示例只在本课 `example_data` 中创建文件,该目录已由 `.gitignore` 忽略。 ## 十二、运行方法 在项目根目录运行: ```powershell python .\02_python进阶\2_3_异常处理\exception_example.py ``` 完成练习后运行: ```powershell python .\02_python进阶\2_3_异常处理\practice.py ``` ## 十三、示例运行结果 ```text 一、处理整数转换异常 “五”不是有效整数。 有效结果:5 无效结果:None ============================== 二、处理除以零异常 Agent 数量不能为 0。 正常结果:2.0 异常结果:None ============================== 三、处理文件读取异常 已完成对 agent_count.txt 的读取尝试。 没有找到文件:missing.txt 已完成对 missing.txt 的读取尝试。 文件中的 Agent 数量:3 不存在文件的读取结果:None ``` ## 十四、关键代码解析 ### 14.1 异常类型写在 `except` 后 ```python except ValueError: ``` 它表示这里只处理 `ValueError`。如果发生其他没有处理的异常,Python 仍会显示异常信息,帮助我们发现新的问题。 ### 14.2 异常分支也要明确返回 如果函数成功时返回数字,失败时可以明确返回 `None`: ```python except ValueError: print("请输入有效整数。") return None ``` 调用者随后可以使用: ```python if result is not None: print(result) ``` 注意不能简单写 `if result:`,因为合法数字 `0` 也会被当作假值。 ### 14.3 `return` 后仍会执行 `finally` 即使 `try` 或 `except` 中执行了 `return`,函数真正结束前仍会执行 `finally`。可以运行示例观察这一点。 ## 十五、常见错误 ### 15.1 使用空白 `except` 空白 `except` 会捕获范围过广,让真正的程序错误难以发现。请写出明确异常类型。 ### 15.2 捕获异常后什么都不做 不推荐: ```python except ValueError: pass ``` 用户看不到提示,开发者也不知道数据为什么没有得到处理。 ### 15.3 用异常代替全部条件判断 `try...except` 不能取代正常业务判断。数字是否在 0 到 100 之间仍应使用 `if` 检查。 ### 15.4 把 `try` 包住整个程序 这会让异常来源变得模糊。应只包住可能发生且准备处理的操作。 ### 15.5 把英文错误信息原样交给用户 学习时可以阅读英文异常类型,但面向用户的提示应说明他们可以如何修正输入。 ## 十六、课堂练习 打开 `practice.py`,依次完成: 1. `parse_score(score_text)`:处理整数转换并检查评分范围; 2. `calculate_average(total_score, agent_count)`:处理除以零; 3. `read_score(file_path)`:处理不存在的评分文件; 4. `main()`:准备安全的练习数据并测试正常、异常分支。 练习题面已经明确给出了参数、返回值、输出文字、测试输入和预期结果。请先独立完成,不要直接复制示例函数。 ## 十七、参考答案 参考答案暂不写入练习文件。你完成后,我会从以下三个方面检查: - 正确性:正常和异常分支是否符合预期; - 可读性:`try` 范围、命名和中文提示是否清楚; - 知识掌握:是否能正确选择异常类型,并区分异常处理与条件判断。 ## 十八、本课小结 - 异常是程序运行时出现的非正常情况; - `try` 放置可能发生异常的代码; - `except` 处理明确类型的异常; - `else` 只在没有异常时执行; - `finally` 无论是否发生异常都会执行; - `ValueError`、`ZeroDivisionError` 和 `FileNotFoundError` 对应不同问题; - 异常处理用于应对失败,条件判断用于检查业务规则; - 不要使用空白 `except` 隐藏问题。 ## 十九、验收标准 - 能用自己的话解释异常处理的作用; - 能看懂三个本课异常类型; - 示例程序输出与讲义一致; - 能针对不同错误使用明确的 `except`; - 能说明 `else` 和 `finally` 的执行时机; - `parse_score("0")` 和 `parse_score("100")` 均能返回合法结果; - 非数字和越界评分会给出不同中文提示; - 除数为零时程序不会崩溃; - 文件不存在时程序不会崩溃; - 练习只在 `practice_data` 中写入数据; - 没有使用空白 `except` 或用 `pass` 隐藏异常。