feat(python进阶): 完成第三至第八课教学内容
This commit is contained in:
2
02_python进阶/2_3_异常处理/.gitignore
vendored
Normal file
2
02_python进阶/2_3_异常处理/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
example_data/
|
||||
practice_data/
|
||||
341
02_python进阶/2_3_异常处理/README.md
Normal file
341
02_python进阶/2_3_异常处理/README.md
Normal file
@@ -0,0 +1,341 @@
|
||||
# 第 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` 隐藏异常。
|
||||
90
02_python进阶/2_3_异常处理/exception_example.py
Normal file
90
02_python进阶/2_3_异常处理/exception_example.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# 第 2-3 课完整示例:异常处理
|
||||
#
|
||||
# 本程序通过固定测试数据演示异常处理,不会要求用户输入。
|
||||
# 文件示例只会读取当前课程目录中的 example_data 文件夹。
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
LESSON_DIR = Path(__file__).parent
|
||||
DATA_DIR = LESSON_DIR / "example_data"
|
||||
CONFIG_FILE = DATA_DIR / "agent_count.txt"
|
||||
|
||||
|
||||
def parse_positive_integer(number_text):
|
||||
"""把文本转换为正整数,失败时返回 None。"""
|
||||
try:
|
||||
number = int(number_text)
|
||||
except ValueError:
|
||||
# ValueError 表示值的内容不符合转换要求。
|
||||
print(f"“{number_text}”不是有效整数。")
|
||||
return None
|
||||
|
||||
if number <= 0:
|
||||
print("数字必须大于 0。")
|
||||
return None
|
||||
|
||||
return number
|
||||
|
||||
|
||||
def divide_tool_count(tool_count, agent_count):
|
||||
"""计算平均工具数量,除数为零时返回 None。"""
|
||||
try:
|
||||
average = tool_count / agent_count
|
||||
except ZeroDivisionError:
|
||||
# ZeroDivisionError 表示程序尝试用数字除以零。
|
||||
print("Agent 数量不能为 0。")
|
||||
return None
|
||||
else:
|
||||
# 只有 try 中没有发生异常时,才会执行 else。
|
||||
return average
|
||||
|
||||
|
||||
def read_agent_count(file_path):
|
||||
"""读取文件中的 Agent 数量,并保证输出结束提示。"""
|
||||
try:
|
||||
content = file_path.read_text(encoding="utf-8")
|
||||
return int(content.strip())
|
||||
except FileNotFoundError:
|
||||
print(f"没有找到文件:{file_path.name}")
|
||||
except ValueError:
|
||||
print(f"{file_path.name} 中保存的不是有效整数。")
|
||||
finally:
|
||||
# 无论是否发生异常,finally 都会执行。
|
||||
print(f"已完成对 {file_path.name} 的读取尝试。")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def prepare_example_file():
|
||||
"""在课程专用目录中准备安全的示例文件。"""
|
||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
CONFIG_FILE.write_text("3\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main():
|
||||
"""依次运行整数转换、除法和文件读取示例。"""
|
||||
print("一、处理整数转换异常")
|
||||
valid_number = parse_positive_integer("5")
|
||||
invalid_number = parse_positive_integer("五")
|
||||
print(f"有效结果:{valid_number}")
|
||||
print(f"无效结果:{invalid_number}")
|
||||
|
||||
print("=" * 30)
|
||||
print("二、处理除以零异常")
|
||||
average = divide_tool_count(6, 3)
|
||||
failed_average = divide_tool_count(6, 0)
|
||||
print(f"正常结果:{average}")
|
||||
print(f"异常结果:{failed_average}")
|
||||
|
||||
print("=" * 30)
|
||||
print("三、处理文件读取异常")
|
||||
prepare_example_file()
|
||||
agent_count = read_agent_count(CONFIG_FILE)
|
||||
missing_count = read_agent_count(DATA_DIR / "missing.txt")
|
||||
print(f"文件中的 Agent 数量:{agent_count}")
|
||||
print(f"不存在文件的读取结果:{missing_count}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
58
02_python进阶/2_3_异常处理/practice.py
Normal file
58
02_python进阶/2_3_异常处理/practice.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# 第 2-3 课课堂练习:异常处理
|
||||
#
|
||||
# 请先阅读讲义并运行完整示例,再按照顺序完成练习。
|
||||
# 不要删除题目、测试数据、预期结果和自查注释。
|
||||
# 本练习只读取当前课程目录中的 practice_data 文件,不删除或覆盖其他文件。
|
||||
|
||||
# 第一部分:定义 parse_score(score_text) 函数
|
||||
# 1. 参数 score_text 接收一个字符串。
|
||||
# 2. 在 try 中使用 int(score_text) 转换为整数,并保存结果。
|
||||
# 3. 捕获 ValueError,输出“评分必须是整数。”,然后 return None。
|
||||
# 4. 转换成功后,检查评分是否处于 0 到 100 之间。
|
||||
# 5. 超出范围时输出“评分必须在 0 到 100 之间。”,然后 return None。
|
||||
# 6. 合法时 return 转换后的整数,不要在函数内输出合法结果。
|
||||
# 7. 分别传入 "90"、"优秀"、"120",预期返回 90、None、None。
|
||||
|
||||
# 第二部分:定义 calculate_average(total_score, agent_count) 函数
|
||||
# 1. 在 try 中计算 total_score / agent_count。
|
||||
# 2. 捕获 ZeroDivisionError,输出“Agent 数量不能为 0。”并 return None。
|
||||
# 3. 使用 else return 平均分。
|
||||
# 4. 传入 180 和 2,预期返回 90.0。
|
||||
# 5. 传入 180 和 0,预期返回 None,程序不能崩溃。
|
||||
|
||||
# 第三部分:定义 read_score(file_path) 函数
|
||||
# 1. 在 try 中使用 read_text(encoding="utf-8") 读取文本。
|
||||
# 2. 调用 strip() 清理文本,再调用 parse_score() 并 return 结果。
|
||||
# 3. 捕获 FileNotFoundError,输出“没有找到评分文件。”并 return None。
|
||||
# 4. 添加 finally,并输出“评分文件读取结束。”。
|
||||
# 5. 注意:parse_score() 已经负责处理 ValueError,不要重复捕获同一错误。
|
||||
|
||||
# 第四部分:定义 main() 函数
|
||||
# 1. 使用 mkdir(parents=True, exist_ok=True) 创建 PRACTICE_DIR。
|
||||
# 2. 使用 write_text() 把 "90\n" 写入 SCORE_FILE,并指定 utf-8。
|
||||
# 3. 调用 read_score(SCORE_FILE),把返回值保存为 saved_score。
|
||||
# 4. saved_score 不是 None 时,输出“读取到的评分:90”。
|
||||
# 5. 再调用 read_score(PRACTICE_DIR / "missing.txt") 测试文件不存在的分支。
|
||||
# 6. 添加程序入口判断,直接运行本文件时调用 main()。
|
||||
|
||||
# 最终验收测试:
|
||||
# 1. parse_score("0") 返回 0;
|
||||
# 2. parse_score("100") 返回 100;
|
||||
# 3. parse_score("优秀") 输出提示并返回 None;
|
||||
# 4. parse_score("-1") 和 parse_score("101") 均返回 None;
|
||||
# 5. calculate_average(180, 2) 返回 90.0;
|
||||
# 6. calculate_average(180, 0) 不崩溃并返回 None;
|
||||
# 7. 存在的评分文件可以正常读取;
|
||||
# 8. 不存在的文件会输出提示,finally 中的文字仍然出现;
|
||||
# 9. 所有异常都捕获了明确类型,没有使用空白 except。
|
||||
|
||||
|
||||
# 完成后自查:
|
||||
# 1. try 中是否只放可能发生异常的代码;
|
||||
# 2. 是否分别理解 ValueError、ZeroDivisionError 和 FileNotFoundError;
|
||||
# 3. 出现异常后,程序是否给出中文提示而不是直接崩溃;
|
||||
# 4. 是否使用 return 返回结果,而不是只在函数中 print();
|
||||
# 5. 是否理解 else 只在没有异常时执行;
|
||||
# 6. 是否理解 finally 无论有无异常都会执行;
|
||||
# 7. 是否没有使用 except Exception 隐藏所有问题;
|
||||
# 8. 是否没有删除题目和验收说明。
|
||||
Reference in New Issue
Block a user