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. 是否没有删除题目和验收说明。
|
||||||
355
02_python进阶/2_4_推导式与简化写法/README.md
Normal file
355
02_python进阶/2_4_推导式与简化写法/README.md
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
# 第 2-4 课:推导式与简化写法
|
||||||
|
|
||||||
|
## 一、本课目标
|
||||||
|
|
||||||
|
完成本课后,你将能够:
|
||||||
|
|
||||||
|
1. 解释推导式是什么,以及它适合解决什么问题;
|
||||||
|
2. 使用列表推导式转换和筛选数据;
|
||||||
|
3. 使用字典推导式创建键值对应关系;
|
||||||
|
4. 使用集合推导式收集不重复的数据;
|
||||||
|
5. 使用简单的条件表达式返回二选一结果;
|
||||||
|
6. 在普通循环和推导式之间选择更清晰的写法;
|
||||||
|
7. 避免编写难以阅读的复杂推导式。
|
||||||
|
|
||||||
|
## 二、前置知识
|
||||||
|
|
||||||
|
学习本课前,需要掌握:
|
||||||
|
|
||||||
|
- 列表、字典和集合;
|
||||||
|
- `for` 循环和 `if` 条件判断;
|
||||||
|
- 函数、参数和返回值;
|
||||||
|
- 字典的 `get()` 方法。
|
||||||
|
|
||||||
|
## 三、推导式是什么
|
||||||
|
|
||||||
|
推导式(Comprehension)是 Python 根据可遍历数据快速创建新集合的一种写法。
|
||||||
|
|
||||||
|
第一阶段中,我们使用普通循环生成新列表:
|
||||||
|
|
||||||
|
```python
|
||||||
|
agent_names = []
|
||||||
|
|
||||||
|
for agent in agents:
|
||||||
|
agent_names.append(agent["name"])
|
||||||
|
```
|
||||||
|
|
||||||
|
使用列表推导式可以写成:
|
||||||
|
|
||||||
|
```python
|
||||||
|
agent_names = [agent["name"] for agent in agents]
|
||||||
|
```
|
||||||
|
|
||||||
|
两段代码的结果相同。推导式没有改变原来的 `agents`,而是创建并返回一个新列表。
|
||||||
|
|
||||||
|
## 四、列表推导式的基本结构
|
||||||
|
|
||||||
|
```python
|
||||||
|
[需要保存的值 for 临时变量 in 可遍历数据]
|
||||||
|
```
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
agent_names = [agent["name"] for agent in agents]
|
||||||
|
```
|
||||||
|
|
||||||
|
建议从右向左理解:
|
||||||
|
|
||||||
|
1. `for agent in agents`:依次取出每个 Agent;
|
||||||
|
2. `agent["name"]`:取得当前 Agent 的名称;
|
||||||
|
3. `[...]`:把每次得到的名称放入新列表。
|
||||||
|
|
||||||
|
推导式中的 `agent` 只是本次遍历使用的临时变量。
|
||||||
|
|
||||||
|
## 五、使用列表推导式筛选数据
|
||||||
|
|
||||||
|
普通循环写法:
|
||||||
|
|
||||||
|
```python
|
||||||
|
enabled_names = []
|
||||||
|
|
||||||
|
for agent in agents:
|
||||||
|
if agent["enabled"]:
|
||||||
|
enabled_names.append(agent["name"])
|
||||||
|
```
|
||||||
|
|
||||||
|
列表推导式写法:
|
||||||
|
|
||||||
|
```python
|
||||||
|
enabled_names = [
|
||||||
|
agent["name"]
|
||||||
|
for agent in agents
|
||||||
|
if agent["enabled"]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
带筛选条件的结构是:
|
||||||
|
|
||||||
|
```python
|
||||||
|
[需要保存的值 for 临时变量 in 可遍历数据 if 条件]
|
||||||
|
```
|
||||||
|
|
||||||
|
只有条件为 `True` 的数据才会进入新列表。
|
||||||
|
|
||||||
|
## 六、转换与筛选的区别
|
||||||
|
|
||||||
|
转换表示每条数据都参与,但保存为另一种形式:
|
||||||
|
|
||||||
|
```python
|
||||||
|
name_lengths = [len(agent["name"]) for agent in agents]
|
||||||
|
```
|
||||||
|
|
||||||
|
筛选表示只有符合条件的数据才保留:
|
||||||
|
|
||||||
|
```python
|
||||||
|
enabled_agents = [agent for agent in agents if agent["enabled"]]
|
||||||
|
```
|
||||||
|
|
||||||
|
一个推导式也可以同时转换和筛选:
|
||||||
|
|
||||||
|
```python
|
||||||
|
enabled_names = [
|
||||||
|
agent["name"]
|
||||||
|
for agent in agents
|
||||||
|
if agent["enabled"]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 七、字典推导式
|
||||||
|
|
||||||
|
字典推导式(Dictionary Comprehension)用于创建新字典:
|
||||||
|
|
||||||
|
```python
|
||||||
|
tool_counts = {
|
||||||
|
agent["name"]: len(agent.get("tools", []))
|
||||||
|
for agent in agents
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
基本结构:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{键: 值 for 临时变量 in 可遍历数据}
|
||||||
|
```
|
||||||
|
|
||||||
|
上述示例的结果类似:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"代码助手": 2,
|
||||||
|
"聊天助手": 1,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字典的键不能重复。如果多个数据生成同一个键,后生成的值会覆盖之前的值。因此,应先确认选作键的数据具有合适的唯一性。
|
||||||
|
|
||||||
|
## 八、集合推导式
|
||||||
|
|
||||||
|
集合推导式(Set Comprehension)用于创建不包含重复值的集合:
|
||||||
|
|
||||||
|
```python
|
||||||
|
models = {agent["model"] for agent in agents}
|
||||||
|
```
|
||||||
|
|
||||||
|
基本结构:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{需要保存的值 for 临时变量 in 可遍历数据}
|
||||||
|
```
|
||||||
|
|
||||||
|
集合与字典都使用花括号,但字典推导式包含 `键: 值`,集合推导式只有一个值。
|
||||||
|
|
||||||
|
如果每个 Agent 中还有一个工具列表,可以使用两层 `for`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
unique_tools = {
|
||||||
|
tool
|
||||||
|
for agent in agents
|
||||||
|
for tool in agent.get("tools", [])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
它等价于:
|
||||||
|
|
||||||
|
```python
|
||||||
|
unique_tools = set()
|
||||||
|
|
||||||
|
for agent in agents:
|
||||||
|
for tool in agent.get("tools", []):
|
||||||
|
unique_tools.add(tool)
|
||||||
|
```
|
||||||
|
|
||||||
|
本课只练习这一种容易理解的两层结构,不加入更多条件。
|
||||||
|
|
||||||
|
## 九、条件表达式
|
||||||
|
|
||||||
|
条件表达式(Conditional Expression)可以根据一个条件从两个值中选择一个:
|
||||||
|
|
||||||
|
```python
|
||||||
|
status_text = "启用" if enabled else "停用"
|
||||||
|
```
|
||||||
|
|
||||||
|
执行顺序:
|
||||||
|
|
||||||
|
1. 检查 `enabled`;
|
||||||
|
2. 条件为 `True`,得到 `"启用"`;
|
||||||
|
3. 条件为 `False`,得到 `"停用"`。
|
||||||
|
|
||||||
|
它等价于:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if enabled:
|
||||||
|
status_text = "启用"
|
||||||
|
else:
|
||||||
|
status_text = "停用"
|
||||||
|
```
|
||||||
|
|
||||||
|
条件表达式适合简单的二选一。如果每个分支需要执行多步操作,应继续使用普通 `if...else`。
|
||||||
|
|
||||||
|
## 十、筛选条件与条件表达式不要混淆
|
||||||
|
|
||||||
|
筛选只决定某条数据是否保留:
|
||||||
|
|
||||||
|
```python
|
||||||
|
[agent["name"] for agent in agents if agent["enabled"]]
|
||||||
|
```
|
||||||
|
|
||||||
|
条件表达式会为每条数据选择一个结果:
|
||||||
|
|
||||||
|
```python
|
||||||
|
[
|
||||||
|
"启用" if agent["enabled"] else "停用"
|
||||||
|
for agent in agents
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
第一段结果数量可能减少,第二段会为每个 Agent 产生一个状态文字。
|
||||||
|
|
||||||
|
## 十一、什么时候使用普通循环
|
||||||
|
|
||||||
|
以下情况优先使用普通循环:
|
||||||
|
|
||||||
|
- 每次遍历需要执行多个步骤;
|
||||||
|
- 需要输出中间过程;
|
||||||
|
- 包含多层条件判断;
|
||||||
|
- 推导式换行后仍难以理解;
|
||||||
|
- 需要在循环中提前 `break` 或 `return`。
|
||||||
|
|
||||||
|
代码行数更少不代表代码更好。能够让自己和其他人快速理解的写法才更合适。
|
||||||
|
|
||||||
|
## 十二、完整示例
|
||||||
|
|
||||||
|
示例文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
02_python进阶/2_4_推导式与简化写法/comprehension_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
示例会演示:
|
||||||
|
|
||||||
|
1. 普通循环与列表推导式得到相同结果;
|
||||||
|
2. 筛选已启用的 Agent 名称;
|
||||||
|
3. 创建名称到工具数量的字典;
|
||||||
|
4. 收集所有不重复工具;
|
||||||
|
5. 使用条件表达式显示 Agent 状态。
|
||||||
|
|
||||||
|
## 十三、运行方法
|
||||||
|
|
||||||
|
在项目根目录运行示例:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_4_推导式与简化写法\comprehension_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
完成练习后运行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_4_推导式与简化写法\practice.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十四、预期结果
|
||||||
|
|
||||||
|
```text
|
||||||
|
普通循环结果:['代码助手', '测试助手']
|
||||||
|
列表推导式结果:['代码助手', '测试助手']
|
||||||
|
工具数量字典:{'代码助手': 2, '聊天助手': 1, '测试助手': 2}
|
||||||
|
不重复工具:['搜索', '测试', '终端']
|
||||||
|
Agent 状态:
|
||||||
|
- 代码助手:启用
|
||||||
|
- 聊天助手:停用
|
||||||
|
- 测试助手:启用
|
||||||
|
```
|
||||||
|
|
||||||
|
集合没有固定顺序。示例使用 `sorted()` 只是为了让输出顺序稳定,便于核对;集合本身仍然不保证顺序。
|
||||||
|
|
||||||
|
## 十五、常见错误
|
||||||
|
|
||||||
|
### 15.1 忘记最外层括号
|
||||||
|
|
||||||
|
列表推导式使用方括号,字典和集合推导式使用花括号。
|
||||||
|
|
||||||
|
### 15.2 把筛选条件写到错误位置
|
||||||
|
|
||||||
|
筛选条件写在 `for...in...` 后面:
|
||||||
|
|
||||||
|
```python
|
||||||
|
[agent["name"] for agent in agents if agent["enabled"]]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 15.3 混淆字典和集合推导式
|
||||||
|
|
||||||
|
```python
|
||||||
|
{agent["name"] for agent in agents}
|
||||||
|
```
|
||||||
|
|
||||||
|
这是集合,因为没有 `键: 值`。创建字典必须写冒号。
|
||||||
|
|
||||||
|
### 15.4 推导式过于复杂
|
||||||
|
|
||||||
|
不要在一个推导式中堆叠多层条件表达式。看不懂时应立即改回普通循环。
|
||||||
|
|
||||||
|
### 15.5 误以为推导式会修改原数据
|
||||||
|
|
||||||
|
推导式通常创建新列表、新字典或新集合。除非其中主动修改对象,否则原集合不会因为推导式本身被替换。
|
||||||
|
|
||||||
|
## 十六、课堂练习
|
||||||
|
|
||||||
|
打开 `practice.py`,依次完成:
|
||||||
|
|
||||||
|
1. 创建全部 Agent 名称列表;
|
||||||
|
2. 筛选已启用 Agent 的名称;
|
||||||
|
3. 创建名称到模型的字典;
|
||||||
|
4. 收集不重复工具;
|
||||||
|
5. 使用条件表达式返回状态文字;
|
||||||
|
6. 在 `main()` 中保存并输出所有返回结果。
|
||||||
|
|
||||||
|
题目已经给出参数、返回值、示例数据和预期结果。请先独立完成。
|
||||||
|
|
||||||
|
## 十七、参考答案
|
||||||
|
|
||||||
|
参考答案暂不写入练习文件。你完成后,我会从正确性、可读性和知识掌握情况三个方面验证,并重点检查你是否能够解释推导式,而不只是记住格式。
|
||||||
|
|
||||||
|
## 十八、本课小结
|
||||||
|
|
||||||
|
- 推导式可以根据已有数据创建新集合;
|
||||||
|
- 列表推导式使用 `[]`;
|
||||||
|
- 字典推导式使用 `{键: 值}`;
|
||||||
|
- 集合推导式使用 `{值}`;
|
||||||
|
- 末尾的 `if` 用于筛选;
|
||||||
|
- 条件表达式用于从两个结果中选择一个;
|
||||||
|
- 推导式适合简单逻辑,复杂逻辑应使用普通循环;
|
||||||
|
- 清晰比简短更重要。
|
||||||
|
|
||||||
|
## 十九、验收标准
|
||||||
|
|
||||||
|
- 能用普通循环和列表推导式实现相同功能;
|
||||||
|
- 能解释列表推导式各部分的执行顺序;
|
||||||
|
- 能使用末尾 `if` 筛选数据;
|
||||||
|
- 能创建名称到模型的字典;
|
||||||
|
- 能使用集合推导式去重;
|
||||||
|
- 能正确使用简单条件表达式;
|
||||||
|
- 能区分筛选条件与条件表达式;
|
||||||
|
- 所有练习函数通过 `return` 返回结果;
|
||||||
|
- 推导式不会修改原始 Agent 列表;
|
||||||
|
- 没有为了简短而编写难以阅读的复杂表达式。
|
||||||
77
02_python进阶/2_4_推导式与简化写法/comprehension_example.py
Normal file
77
02_python进阶/2_4_推导式与简化写法/comprehension_example.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# 第 2-4 课完整示例:推导式与简化写法
|
||||||
|
#
|
||||||
|
# 本示例先展示普通循环,再展示作用相同的推导式。
|
||||||
|
# 推导式适合简单的数据转换和筛选,不应为了缩短代码而堆叠复杂逻辑。
|
||||||
|
|
||||||
|
|
||||||
|
def get_enabled_agent_names(agents):
|
||||||
|
"""使用列表推导式返回所有已启用 Agent 的名称。"""
|
||||||
|
return [agent["name"] for agent in agents if agent["enabled"]]
|
||||||
|
|
||||||
|
|
||||||
|
def get_agent_tool_counts(agents):
|
||||||
|
"""使用字典推导式建立 Agent 名称与工具数量的对应关系。"""
|
||||||
|
return {
|
||||||
|
agent["name"]: len(agent.get("tools", []))
|
||||||
|
for agent in agents
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_unique_tools(agents):
|
||||||
|
"""使用集合推导式收集不重复的工具名称。"""
|
||||||
|
return {
|
||||||
|
tool
|
||||||
|
for agent in agents
|
||||||
|
for tool in agent.get("tools", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_status_text(agent):
|
||||||
|
"""使用条件表达式返回简短的中文状态。"""
|
||||||
|
return "启用" if agent["enabled"] else "停用"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""准备 Agent 数据,并演示四种简化写法。"""
|
||||||
|
agents = [
|
||||||
|
{
|
||||||
|
"name": "代码助手",
|
||||||
|
"enabled": True,
|
||||||
|
"tools": ["搜索", "终端"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "聊天助手",
|
||||||
|
"enabled": False,
|
||||||
|
"tools": ["搜索"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "测试助手",
|
||||||
|
"enabled": True,
|
||||||
|
"tools": ["终端", "测试"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# 普通循环写法:先创建空列表,再逐条判断和添加。
|
||||||
|
names_from_loop = []
|
||||||
|
for agent in agents:
|
||||||
|
if agent["enabled"]:
|
||||||
|
names_from_loop.append(agent["name"])
|
||||||
|
|
||||||
|
# 列表推导式可以简洁表达同一个“遍历、筛选、保存”过程。
|
||||||
|
enabled_names = get_enabled_agent_names(agents)
|
||||||
|
tool_counts = get_agent_tool_counts(agents)
|
||||||
|
unique_tools = get_unique_tools(agents)
|
||||||
|
|
||||||
|
print(f"普通循环结果:{names_from_loop}")
|
||||||
|
print(f"列表推导式结果:{enabled_names}")
|
||||||
|
print(f"工具数量字典:{tool_counts}")
|
||||||
|
print(f"不重复工具:{sorted(unique_tools)}")
|
||||||
|
print("Agent 状态:")
|
||||||
|
|
||||||
|
for agent in agents:
|
||||||
|
status_text = get_status_text(agent)
|
||||||
|
print(f"- {agent['name']}:{status_text}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
65
02_python进阶/2_4_推导式与简化写法/practice.py
Normal file
65
02_python进阶/2_4_推导式与简化写法/practice.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# 第 2-4 课课堂练习:推导式与简化写法
|
||||||
|
#
|
||||||
|
# 请先阅读讲义并运行完整示例,再按照题目顺序完成。
|
||||||
|
# 每个函数都需要使用 return 返回结果,不要只在函数中 print()。
|
||||||
|
# 不要删除题目、测试数据、预期结果和自查注释。
|
||||||
|
|
||||||
|
|
||||||
|
# 第一部分:定义 get_agent_names(agents) 函数
|
||||||
|
# 1. 使用列表推导式遍历 agents。
|
||||||
|
# 2. 取出每个字典中的 name。
|
||||||
|
# 3. return 生成的新列表,不要修改原列表。
|
||||||
|
# 4. 调用后保存结果并输出。
|
||||||
|
# 5. 预期结果:['代码助手', '聊天助手', '测试助手']。
|
||||||
|
|
||||||
|
# 第二部分:定义 get_enabled_agent_names(agents) 函数
|
||||||
|
# 1. 使用带 if 的列表推导式。
|
||||||
|
# 2. 只保留 enabled 为 True 的 Agent 名称。
|
||||||
|
# 3. return 新列表。
|
||||||
|
# 4. 预期结果:['代码助手', '测试助手']。
|
||||||
|
|
||||||
|
# 第三部分:定义 get_model_mapping(agents) 函数
|
||||||
|
# 1. 使用字典推导式。
|
||||||
|
# 2. 字典的键是 Agent 名称,值是模型名称。
|
||||||
|
# 3. return 新字典。
|
||||||
|
# 4. 预期结果:
|
||||||
|
# {'代码助手': 'gpt-5', '聊天助手': 'o3', '测试助手': 'gpt-5'}。
|
||||||
|
|
||||||
|
# 第四部分:定义 get_unique_tools(agents) 函数
|
||||||
|
# 1. 使用集合推导式和两层 for。
|
||||||
|
# 2. 外层遍历 agents,内层遍历每个 agent.get("tools", [])。
|
||||||
|
# 3. return 不重复的工具集合。
|
||||||
|
# 4. 预期集合包含:搜索、终端、测试。
|
||||||
|
# 5. 集合没有固定顺序,验收时比较内容,不比较显示顺序。
|
||||||
|
|
||||||
|
# 第五部分:定义 get_status_text(enabled) 函数
|
||||||
|
# 1. 使用条件表达式,不使用多行 if...else。
|
||||||
|
# 2. enabled 为 True 时 return "启用",否则 return "停用"。
|
||||||
|
# 3. 分别传入 True 和 False。
|
||||||
|
# 4. 预期结果分别是“启用”和“停用”。
|
||||||
|
|
||||||
|
# 第六部分:定义 main() 函数
|
||||||
|
# 1. 依次调用前面五个函数,并把每个返回值保存到有意义的变量中。
|
||||||
|
# 2. 使用 print() 输出所有 Agent 名称、启用名称、模型字典和工具集合。
|
||||||
|
# 3. 调用 get_status_text(agents[0]["enabled"]) 并输出第一个 Agent 的状态。
|
||||||
|
# 4. 添加程序入口判断,直接运行本文件时调用 main()。
|
||||||
|
|
||||||
|
# 最终验收测试:
|
||||||
|
# 1. get_agent_names(agents) 返回三个名称;
|
||||||
|
# 2. get_enabled_agent_names(agents) 只返回两个启用的名称;
|
||||||
|
# 3. get_model_mapping(agents) 返回名称到模型的正确映射;
|
||||||
|
# 4. get_unique_tools(agents) 返回三个不重复工具;
|
||||||
|
# 5. get_status_text(True) 返回“启用”;
|
||||||
|
# 6. get_status_text(False) 返回“停用”;
|
||||||
|
# 7. 所有函数都使用 return 返回结果;
|
||||||
|
# 8. 推导式没有修改原始 agents;
|
||||||
|
# 9. 没有使用嵌套条件表达式或难以阅读的复杂推导式。
|
||||||
|
|
||||||
|
|
||||||
|
# 完成后自查:
|
||||||
|
# 1. 是否能从左到右解释每个推导式的执行过程;
|
||||||
|
# 2. 是否分清列表、字典和集合推导式使用的括号;
|
||||||
|
# 3. 是否知道筛选条件写在推导式末尾;
|
||||||
|
# 4. 是否知道条件表达式的结果分别写在 if 两侧;
|
||||||
|
# 5. 是否优先保证可读性,而不是一味缩短代码;
|
||||||
|
# 6. 是否保留了完整题目和验收说明。
|
||||||
371
02_python进阶/2_5_迭代器与生成器/README.md
Normal file
371
02_python进阶/2_5_迭代器与生成器/README.md
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
# 第 2-5 课:迭代器与生成器
|
||||||
|
|
||||||
|
## 一、本课目标
|
||||||
|
|
||||||
|
完成本课后,你将能够:
|
||||||
|
|
||||||
|
1. 解释可迭代对象、迭代器和生成器分别是什么;
|
||||||
|
2. 使用 `iter()` 创建迭代器;
|
||||||
|
3. 使用 `next()` 逐个取得数据;
|
||||||
|
4. 理解数据取完时出现的 `StopIteration`;
|
||||||
|
5. 使用包含 `yield` 的生成器函数逐个产生结果;
|
||||||
|
6. 说明生成器为什么适合处理大量数据;
|
||||||
|
7. 理解同一个生成器被消费后不能自动回到开头。
|
||||||
|
|
||||||
|
## 二、前置知识
|
||||||
|
|
||||||
|
学习本课前,需要掌握:
|
||||||
|
|
||||||
|
- 列表、字典和集合;
|
||||||
|
- `for` 循环和 `while` 循环;
|
||||||
|
- 函数、参数和返回值;
|
||||||
|
- `try...except` 异常处理;
|
||||||
|
- 列表推导式。
|
||||||
|
|
||||||
|
## 三、从 `for` 循环开始理解
|
||||||
|
|
||||||
|
我们已经多次使用 `for` 遍历列表:
|
||||||
|
|
||||||
|
```python
|
||||||
|
agent_names = ["代码助手", "聊天助手", "测试助手"]
|
||||||
|
|
||||||
|
for agent_name in agent_names:
|
||||||
|
print(agent_name)
|
||||||
|
```
|
||||||
|
|
||||||
|
列表可以依次提供其中的数据,因此它是可迭代对象(Iterable)。可迭代对象可以暂时理解为“能够被 `for` 循环逐项访问的数据”。
|
||||||
|
|
||||||
|
目前学过的常见可迭代对象包括:
|
||||||
|
|
||||||
|
- 字符串;
|
||||||
|
- 列表;
|
||||||
|
- 元组;
|
||||||
|
- 字典;
|
||||||
|
- 集合;
|
||||||
|
- `range()` 产生的对象。
|
||||||
|
|
||||||
|
## 四、迭代器是什么
|
||||||
|
|
||||||
|
迭代器(Iterator)是记录当前遍历位置,并能提供下一个值的对象。
|
||||||
|
|
||||||
|
使用 `iter()` 可以根据可迭代对象创建迭代器:
|
||||||
|
|
||||||
|
```python
|
||||||
|
agent_names = ["代码助手", "聊天助手"]
|
||||||
|
name_iterator = iter(agent_names)
|
||||||
|
```
|
||||||
|
|
||||||
|
使用 `next()` 逐个取得值:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print(next(name_iterator))
|
||||||
|
print(next(name_iterator))
|
||||||
|
```
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
代码助手
|
||||||
|
聊天助手
|
||||||
|
```
|
||||||
|
|
||||||
|
每调用一次 `next()`,迭代器都会向后移动一步。它不会自动回到开头。
|
||||||
|
|
||||||
|
## 五、`StopIteration`
|
||||||
|
|
||||||
|
当迭代器中的数据全部取完后,再调用 `next()` 会出现:
|
||||||
|
|
||||||
|
```text
|
||||||
|
StopIteration
|
||||||
|
```
|
||||||
|
|
||||||
|
停止迭代异常(Stop Iteration,`StopIteration`)表示迭代器已经没有下一个值。
|
||||||
|
|
||||||
|
可以手动处理:
|
||||||
|
|
||||||
|
```python
|
||||||
|
try:
|
||||||
|
print(next(name_iterator))
|
||||||
|
except StopIteration:
|
||||||
|
print("数据已经全部取完。")
|
||||||
|
```
|
||||||
|
|
||||||
|
`for` 循环实际上会在内部不断取得下一个值,并在遇到 `StopIteration` 时自动结束,所以平时使用 `for` 不需要自己捕获它。
|
||||||
|
|
||||||
|
## 六、手动还原 `for` 循环的过程
|
||||||
|
|
||||||
|
下面两段代码表达相似的过程。
|
||||||
|
|
||||||
|
使用 `for`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
for value in values:
|
||||||
|
print(value)
|
||||||
|
```
|
||||||
|
|
||||||
|
手动使用迭代器:
|
||||||
|
|
||||||
|
```python
|
||||||
|
value_iterator = iter(values)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
value = next(value_iterator)
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
|
||||||
|
print(value)
|
||||||
|
```
|
||||||
|
|
||||||
|
真实的 `for` 循环由 Python 自动完成这些步骤。本课手动编写一次,是为了理解其工作方式,而不是建议以后都替换 `for`。
|
||||||
|
|
||||||
|
## 七、生成器是什么
|
||||||
|
|
||||||
|
生成器(Generator)是一种特殊的迭代器。它可以按需要逐个产生数据,不必先把全部结果存入列表。
|
||||||
|
|
||||||
|
包含 `yield` 的函数称为生成器函数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def generate_numbers():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
yield 3
|
||||||
|
```
|
||||||
|
|
||||||
|
调用普通函数时,函数会立即执行并返回结果。调用生成器函数时,Python 先创建生成器对象:
|
||||||
|
|
||||||
|
```python
|
||||||
|
number_generator = generate_numbers()
|
||||||
|
```
|
||||||
|
|
||||||
|
这时函数体还没有完整执行。可以用 `next()` 或 `for` 逐个取得值:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print(next(number_generator))
|
||||||
|
print(next(number_generator))
|
||||||
|
```
|
||||||
|
|
||||||
|
## 八、`yield` 与 `return` 的区别
|
||||||
|
|
||||||
|
普通函数使用 `return`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_names(agents):
|
||||||
|
names = []
|
||||||
|
|
||||||
|
for agent in agents:
|
||||||
|
names.append(agent["name"])
|
||||||
|
|
||||||
|
return names
|
||||||
|
```
|
||||||
|
|
||||||
|
它会先准备完整列表,然后一次返回。
|
||||||
|
|
||||||
|
生成器函数使用 `yield`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def generate_names(agents):
|
||||||
|
for agent in agents:
|
||||||
|
yield agent["name"]
|
||||||
|
```
|
||||||
|
|
||||||
|
`yield` 会完成三件事:
|
||||||
|
|
||||||
|
1. 交出当前结果;
|
||||||
|
2. 暂停函数;
|
||||||
|
3. 保存当前执行位置,下一次继续从这里向后运行。
|
||||||
|
|
||||||
|
函数中的 `return` 会结束整个函数;`yield` 交出一个值后还能继续生成后面的值。
|
||||||
|
|
||||||
|
## 九、使用 `for` 遍历生成器
|
||||||
|
|
||||||
|
生成器也是迭代器,因此可以直接使用 `for`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
for agent_name in generate_names(agents):
|
||||||
|
print(agent_name)
|
||||||
|
```
|
||||||
|
|
||||||
|
这种写法最常见,也比手动调用 `next()` 更安全。
|
||||||
|
|
||||||
|
如果确实需要把所有结果放入列表,可以使用:
|
||||||
|
|
||||||
|
```python
|
||||||
|
agent_names = list(generate_names(agents))
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:这样会立即消费生成器,并把全部结果保存到内存中。
|
||||||
|
|
||||||
|
## 十、为什么需要生成器
|
||||||
|
|
||||||
|
假设需要处理一百万条记录。列表写法通常先准备一百万个结果,再开始使用;生成器可以产生一条、处理一条。
|
||||||
|
|
||||||
|
这种按需产生数据的方式称为惰性求值(Lazy Evaluation)。这里的“惰性”不是运行缓慢,而是“需要时才计算”。
|
||||||
|
|
||||||
|
生成器的主要优点:
|
||||||
|
|
||||||
|
- 不必一次保存全部结果;
|
||||||
|
- 可以更早开始处理第一条数据;
|
||||||
|
- 适合文件逐行读取、大量查询结果或持续数据流。
|
||||||
|
|
||||||
|
本课使用少量 Agent 数据是为了便于观察。数据量很小时,普通列表通常同样合适。
|
||||||
|
|
||||||
|
## 十一、生成器只能继续向后执行
|
||||||
|
|
||||||
|
```python
|
||||||
|
name_generator = generate_names(agents)
|
||||||
|
|
||||||
|
first_result = list(name_generator)
|
||||||
|
second_result = list(name_generator)
|
||||||
|
```
|
||||||
|
|
||||||
|
第一次已经把生成器消费完,因此第二次得到空列表。
|
||||||
|
|
||||||
|
如果确实需要重新遍历,必须重新调用生成器函数创建一个新生成器:
|
||||||
|
|
||||||
|
```python
|
||||||
|
new_generator = generate_names(agents)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十二、生成器表达式
|
||||||
|
|
||||||
|
上一课学习了列表推导式:
|
||||||
|
|
||||||
|
```python
|
||||||
|
enabled_names = [
|
||||||
|
agent["name"]
|
||||||
|
for agent in agents
|
||||||
|
if agent["enabled"]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
把最外层方括号改成圆括号,会得到生成器表达式(Generator Expression):
|
||||||
|
|
||||||
|
```python
|
||||||
|
enabled_name_generator = (
|
||||||
|
agent["name"]
|
||||||
|
for agent in agents
|
||||||
|
if agent["enabled"]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
列表推导式立即创建完整列表,生成器表达式按需产生结果。本课练习以 `yield` 为主,先把生成器函数的执行过程理解清楚。
|
||||||
|
|
||||||
|
## 十三、完整示例
|
||||||
|
|
||||||
|
示例文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
02_python进阶/2_5_迭代器与生成器/iterator_generator_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
示例依次演示:
|
||||||
|
|
||||||
|
1. 使用 `iter()` 和 `next()` 手动取出名称;
|
||||||
|
2. 捕获数据取完后的 `StopIteration`;
|
||||||
|
3. 使用生成器筛选启用 Agent;
|
||||||
|
4. 使用生成器逐条产生 Agent 报告。
|
||||||
|
|
||||||
|
## 十四、运行方法
|
||||||
|
|
||||||
|
在项目根目录运行示例:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_5_迭代器与生成器\iterator_generator_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
完成练习后运行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_5_迭代器与生成器\practice.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十五、预期结果
|
||||||
|
|
||||||
|
```text
|
||||||
|
一、手动使用迭代器
|
||||||
|
代码助手
|
||||||
|
聊天助手
|
||||||
|
测试助手
|
||||||
|
名称已经全部取完。
|
||||||
|
==============================
|
||||||
|
二、使用生成器函数
|
||||||
|
生成器对象:generator
|
||||||
|
启用 Agent:代码助手
|
||||||
|
启用 Agent:测试助手
|
||||||
|
==============================
|
||||||
|
三、逐条生成 Agent 报告
|
||||||
|
代码助手|状态:启用|工具数量:2
|
||||||
|
聊天助手|状态:停用|工具数量:1
|
||||||
|
测试助手|状态:启用|工具数量:2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十六、常见错误
|
||||||
|
|
||||||
|
### 16.1 对可迭代对象直接使用 `next()`
|
||||||
|
|
||||||
|
列表是可迭代对象,但列表本身不是迭代器。应先调用:
|
||||||
|
|
||||||
|
```python
|
||||||
|
name_iterator = iter(agent_names)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.2 数据取完后继续调用 `next()`
|
||||||
|
|
||||||
|
迭代器不会自动回到开头。数据取完后继续调用会产生 `StopIteration`。
|
||||||
|
|
||||||
|
### 16.3 在生成器函数中使用 `return result_list`
|
||||||
|
|
||||||
|
如果先创建完整列表再返回,就没有实现按需生成。需要逐个结果时应使用 `yield`。
|
||||||
|
|
||||||
|
### 16.4 以为调用生成器函数就会立即执行
|
||||||
|
|
||||||
|
调用生成器函数只会创建生成器对象。第一次调用 `next()` 或开始 `for` 遍历时,函数才真正向前执行。
|
||||||
|
|
||||||
|
### 16.5 重复消费同一个生成器
|
||||||
|
|
||||||
|
同一个生成器不会自动重置。第二次遍历已经消费完的生成器不会再次得到原数据。
|
||||||
|
|
||||||
|
### 16.6 所有地方都改用生成器
|
||||||
|
|
||||||
|
少量数据需要反复读取时,列表可能更直观。生成器适合按顺序处理、无需返回头部或数据量较大的场景。
|
||||||
|
|
||||||
|
## 十七、课堂练习
|
||||||
|
|
||||||
|
打开 `practice.py`,依次完成:
|
||||||
|
|
||||||
|
1. 使用 `iter()` 和 `next()` 取得前两个名称;
|
||||||
|
2. 手动捕获 `StopIteration` 并消费迭代器;
|
||||||
|
3. 使用 `yield` 生成启用 Agent 名称;
|
||||||
|
4. 使用 `yield` 生成所有工具名称;
|
||||||
|
5. 观察同一个生成器被消费两次的结果;
|
||||||
|
6. 在 `main()` 中输出并核对全部结果。
|
||||||
|
|
||||||
|
## 十八、参考答案
|
||||||
|
|
||||||
|
参考答案暂不写入练习文件。完成后,我会从正确性、可读性和知识掌握情况三个方面验证,并检查生成器函数是否真正使用 `yield` 按需产生数据。
|
||||||
|
|
||||||
|
## 十九、本课小结
|
||||||
|
|
||||||
|
- 可迭代对象能被 `for` 逐项访问;
|
||||||
|
- 迭代器记录当前遍历位置;
|
||||||
|
- `iter()` 创建迭代器,`next()` 取得下一个值;
|
||||||
|
- 数据取完时会出现 `StopIteration`;
|
||||||
|
- `for` 循环会自动处理停止迭代;
|
||||||
|
- 包含 `yield` 的函数是生成器函数;
|
||||||
|
- `yield` 交出一个结果并暂停当前函数;
|
||||||
|
- 生成器按需产生数据,适合顺序处理大量内容;
|
||||||
|
- 同一个生成器消费完后不能自动重新开始。
|
||||||
|
|
||||||
|
## 二十、验收标准
|
||||||
|
|
||||||
|
- 能解释可迭代对象与迭代器的区别;
|
||||||
|
- 能使用 `iter()` 和 `next()` 手动取得数据;
|
||||||
|
- 能正确处理 `StopIteration`;
|
||||||
|
- 能解释 `for` 如何自动结束迭代;
|
||||||
|
- 能编写包含 `yield` 的生成器函数;
|
||||||
|
- 能使用 `for` 和 `list()` 消费生成器;
|
||||||
|
- 能解释 `yield` 与 `return` 的区别;
|
||||||
|
- 能说明同一个生成器第二次消费为什么为空;
|
||||||
|
- 生成器函数没有提前创建完整结果列表;
|
||||||
|
- 示例与练习均不会修改原始 Agent 数据。
|
||||||
74
02_python进阶/2_5_迭代器与生成器/iterator_generator_example.py
Normal file
74
02_python进阶/2_5_迭代器与生成器/iterator_generator_example.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# 第 2-5 课完整示例:迭代器与生成器
|
||||||
|
#
|
||||||
|
# 本示例使用少量固定数据观察迭代过程。
|
||||||
|
# 重点不是缩短代码,而是理解数据如何被逐个取出和按需生成。
|
||||||
|
|
||||||
|
|
||||||
|
def generate_enabled_agent_names(agents):
|
||||||
|
"""逐个生成处于启用状态的 Agent 名称。"""
|
||||||
|
for agent in agents:
|
||||||
|
if agent["enabled"]:
|
||||||
|
# yield 每次只交出一个结果,并暂停函数当前的执行位置。
|
||||||
|
yield agent["name"]
|
||||||
|
|
||||||
|
|
||||||
|
def generate_agent_reports(agents):
|
||||||
|
"""逐个生成 Agent 的中文报告。"""
|
||||||
|
for agent in agents:
|
||||||
|
status_text = "启用" if agent["enabled"] else "停用"
|
||||||
|
tool_count = len(agent.get("tools", []))
|
||||||
|
yield (
|
||||||
|
f"{agent['name']}|状态:{status_text}|"
|
||||||
|
f"工具数量:{tool_count}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""演示 iter()、next()、StopIteration 和生成器。"""
|
||||||
|
agents = [
|
||||||
|
{
|
||||||
|
"name": "代码助手",
|
||||||
|
"enabled": True,
|
||||||
|
"tools": ["搜索", "终端"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "聊天助手",
|
||||||
|
"enabled": False,
|
||||||
|
"tools": ["搜索"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "测试助手",
|
||||||
|
"enabled": True,
|
||||||
|
"tools": ["终端", "测试"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
print("一、手动使用迭代器")
|
||||||
|
agent_names = [agent["name"] for agent in agents]
|
||||||
|
name_iterator = iter(agent_names)
|
||||||
|
|
||||||
|
print(next(name_iterator))
|
||||||
|
print(next(name_iterator))
|
||||||
|
print(next(name_iterator))
|
||||||
|
|
||||||
|
try:
|
||||||
|
next(name_iterator)
|
||||||
|
except StopIteration:
|
||||||
|
print("名称已经全部取完。")
|
||||||
|
|
||||||
|
print("=" * 30)
|
||||||
|
print("二、使用生成器函数")
|
||||||
|
enabled_name_generator = generate_enabled_agent_names(agents)
|
||||||
|
print(f"生成器对象:{type(enabled_name_generator).__name__}")
|
||||||
|
|
||||||
|
for agent_name in enabled_name_generator:
|
||||||
|
print(f"启用 Agent:{agent_name}")
|
||||||
|
|
||||||
|
print("=" * 30)
|
||||||
|
print("三、逐条生成 Agent 报告")
|
||||||
|
for report in generate_agent_reports(agents):
|
||||||
|
print(report)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
73
02_python进阶/2_5_迭代器与生成器/practice.py
Normal file
73
02_python进阶/2_5_迭代器与生成器/practice.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# 第 2-5 课课堂练习:迭代器与生成器
|
||||||
|
#
|
||||||
|
# 请先阅读讲义并运行完整示例,再按照题目顺序完成。
|
||||||
|
# 需要生成多个结果的函数必须使用 yield,不要提前创建完整结果列表。
|
||||||
|
# 不要删除题目、测试数据、预期结果和自查注释。
|
||||||
|
|
||||||
|
|
||||||
|
# 第一部分:手动使用迭代器
|
||||||
|
# 1. 定义 show_first_two_names(agents) 函数。
|
||||||
|
# 2. 使用列表推导式取得所有 Agent 名称。
|
||||||
|
# 3. 使用 iter() 把名称列表转换成迭代器,并保存为 name_iterator。
|
||||||
|
# 4. 连续调用两次 next(),分别保存第一个和第二个名称。
|
||||||
|
# 5. return 一个包含两个名称的元组。
|
||||||
|
# 6. 调用函数并保存结果,预期返回 ("代码助手", "聊天助手")。
|
||||||
|
|
||||||
|
# 第二部分:处理 StopIteration
|
||||||
|
# 1. 定义 consume_iterator(values) 函数,参数接收列表。
|
||||||
|
# 2. 使用 iter(values) 创建迭代器。
|
||||||
|
# 3. 使用 while True 持续调用 next(),把每个结果加入 result 列表。
|
||||||
|
# 4. 捕获 StopIteration 后使用 break 结束循环。
|
||||||
|
# 5. return result。
|
||||||
|
# 6. 传入 ["搜索", "终端"],预期返回相同内容的新列表。
|
||||||
|
# 7. 传入空列表 [],预期返回 [],程序不能崩溃。
|
||||||
|
|
||||||
|
# 第三部分:定义 generate_enabled_names(agents) 生成器函数
|
||||||
|
# 1. 使用 for 遍历 agents。
|
||||||
|
# 2. 只处理 enabled 为 True 的 Agent。
|
||||||
|
# 3. 使用 yield 逐个生成 Agent 名称,不要创建结果列表。
|
||||||
|
# 4. 调用函数时先把生成器对象保存为 enabled_name_generator。
|
||||||
|
# 5. 使用 list(enabled_name_generator) 收集结果并输出。
|
||||||
|
# 6. 预期结果:['代码助手', '测试助手']。
|
||||||
|
|
||||||
|
# 第四部分:定义 generate_tool_names(agents) 生成器函数
|
||||||
|
# 1. 使用两层 for:外层遍历 agents,内层遍历 agent.get("tools", [])。
|
||||||
|
# 2. 使用 yield 逐个生成工具名称。
|
||||||
|
# 3. 本练习不去重,重复工具也需要保留。
|
||||||
|
# 4. 使用 list() 收集生成结果。
|
||||||
|
# 5. 预期结果:['搜索', '终端', '搜索', '终端', '测试']。
|
||||||
|
|
||||||
|
# 第五部分:观察生成器只能继续向后取值
|
||||||
|
# 1. 调用 generate_enabled_names(agents) 创建一个新生成器。
|
||||||
|
# 2. 第一次使用 list() 收集并保存结果,预期有两个名称。
|
||||||
|
# 3. 对同一个生成器再次使用 list(),预期得到空列表。
|
||||||
|
# 4. 输出两次结果,并用自己的话解释第二次为什么为空。
|
||||||
|
# 注意:不要为了让第二次也有数据而重新创建生成器,本题就是观察同一个生成器。
|
||||||
|
|
||||||
|
# 第六部分:定义 main() 函数
|
||||||
|
# 1. 依次调用前面四个函数,并把返回值或生成结果保存到有意义的变量中。
|
||||||
|
# 2. 输出前两个名称、迭代器消费结果、启用名称和全部工具名称。
|
||||||
|
# 3. 完成第五部分的两次生成器收集实验。
|
||||||
|
# 4. 添加程序入口判断,直接运行本文件时调用 main()。
|
||||||
|
|
||||||
|
|
||||||
|
# 最终验收测试:
|
||||||
|
# 1. show_first_two_names(agents) 返回正确的两个名称;
|
||||||
|
# 2. consume_iterator(["搜索", "终端"]) 返回相同内容的新列表;
|
||||||
|
# 3. consume_iterator([]) 返回空列表且不报错;
|
||||||
|
# 4. generate_enabled_names() 是包含 yield 的生成器函数;
|
||||||
|
# 5. 启用名称生成结果为代码助手和测试助手;
|
||||||
|
# 6. generate_tool_names() 是包含 yield 的生成器函数;
|
||||||
|
# 7. 工具生成结果保留重复的搜索和终端;
|
||||||
|
# 8. 同一个生成器第一次收集有数据,第二次收集为空;
|
||||||
|
# 9. 生成器函数中没有先创建完整结果列表再 return。
|
||||||
|
|
||||||
|
|
||||||
|
# 完成后自查:
|
||||||
|
# 1. 是否能解释可迭代对象和迭代器的区别;
|
||||||
|
# 2. 是否知道 iter() 创建迭代器、next() 取出下一个值;
|
||||||
|
# 3. 是否知道数据取完后会出现 StopIteration;
|
||||||
|
# 4. 是否理解 for 循环会自动处理 StopIteration;
|
||||||
|
# 5. 是否理解 yield 会交出结果并暂停函数;
|
||||||
|
# 6. 是否知道生成器只能从当前位置继续向后执行;
|
||||||
|
# 7. 是否保留了完整题目和验收说明。
|
||||||
357
02_python进阶/2_6_装饰器/README.md
Normal file
357
02_python进阶/2_6_装饰器/README.md
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
# 第 2-6 课:装饰器
|
||||||
|
|
||||||
|
## 一、本课目标
|
||||||
|
|
||||||
|
完成本课后,你将能够:
|
||||||
|
|
||||||
|
1. 理解函数可以像其他值一样赋值和传递;
|
||||||
|
2. 解释装饰器用于解决什么问题;
|
||||||
|
3. 编写接收函数并返回新函数的简单装饰器;
|
||||||
|
4. 使用 `@装饰器名称` 增强函数;
|
||||||
|
5. 使用 `*args` 和 `**kwargs` 转发参数;
|
||||||
|
6. 保留被装饰函数的返回值;
|
||||||
|
7. 使用 `functools.wraps` 保留原函数信息。
|
||||||
|
|
||||||
|
## 二、前置知识
|
||||||
|
|
||||||
|
学习本课前,需要掌握:
|
||||||
|
|
||||||
|
- 定义和调用函数;
|
||||||
|
- 参数、返回值和局部变量;
|
||||||
|
- `*args` 与 `**kwargs`;
|
||||||
|
- 条件判断;
|
||||||
|
- 模块导入。
|
||||||
|
|
||||||
|
## 三、为什么需要装饰器
|
||||||
|
|
||||||
|
多个函数可能需要执行相同的额外操作,例如:
|
||||||
|
|
||||||
|
- 调用前记录日志;
|
||||||
|
- 检查用户是否有权限;
|
||||||
|
- 检查 Agent 是否启用;
|
||||||
|
- 统计函数执行时间;
|
||||||
|
- 统一处理函数调用结果。
|
||||||
|
|
||||||
|
如果把相同代码复制到每个函数中,修改时容易遗漏。装饰器(Decorator)可以在不修改原函数主体的情况下,为函数统一增加行为。
|
||||||
|
|
||||||
|
本课使用“记录调用日志”和“检查 Agent 状态”两个简单场景。
|
||||||
|
|
||||||
|
## 四、函数也可以作为值
|
||||||
|
|
||||||
|
定义函数后,函数名不加括号表示函数本身:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def greet_agent(name):
|
||||||
|
return f"你好,{name}。"
|
||||||
|
|
||||||
|
|
||||||
|
greeting_function = greet_agent
|
||||||
|
result = greeting_function("代码助手")
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
这里没有调用 `greet_agent`,而是把函数交给另一个变量。随后可以通过新变量调用同一个函数。
|
||||||
|
|
||||||
|
注意区别:
|
||||||
|
|
||||||
|
```python
|
||||||
|
greeting_function = greet_agent # 保存函数本身
|
||||||
|
greeting_result = greet_agent("代码助手") # 立即调用并保存结果
|
||||||
|
```
|
||||||
|
|
||||||
|
装饰器能够工作,正是因为函数可以作为值传入另一个函数,也可以从另一个函数返回。
|
||||||
|
|
||||||
|
## 五、函数内部可以定义函数
|
||||||
|
|
||||||
|
Python 允许在一个函数中定义另一个函数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def outer():
|
||||||
|
def inner():
|
||||||
|
return "内部函数的结果"
|
||||||
|
|
||||||
|
return inner
|
||||||
|
```
|
||||||
|
|
||||||
|
`outer()` 返回的是 `inner` 函数本身,而不是它的调用结果:
|
||||||
|
|
||||||
|
```python
|
||||||
|
inner_function = outer()
|
||||||
|
result = inner_function()
|
||||||
|
```
|
||||||
|
|
||||||
|
装饰器通常会在内部定义一个名为 `wrapper` 的函数。`wrapper` 的中文含义是“包装器”,它会包住原函数的调用过程。
|
||||||
|
|
||||||
|
## 六、第一个装饰器
|
||||||
|
|
||||||
|
```python
|
||||||
|
def log_call(func):
|
||||||
|
def wrapper():
|
||||||
|
print("函数开始执行。")
|
||||||
|
result = func()
|
||||||
|
print("函数执行结束。")
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
逐层理解:
|
||||||
|
|
||||||
|
1. `log_call(func)` 接收需要增强的原函数;
|
||||||
|
2. 内部定义 `wrapper()`;
|
||||||
|
3. `wrapper()` 先执行额外逻辑;
|
||||||
|
4. `func()` 调用原函数;
|
||||||
|
5. 保存并返回原函数结果;
|
||||||
|
6. `log_call()` 最后返回 `wrapper` 函数本身。
|
||||||
|
|
||||||
|
这里必须写:
|
||||||
|
|
||||||
|
```python
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
不要写成 `return wrapper()`,后者会立即调用包装函数。
|
||||||
|
|
||||||
|
## 七、手动使用装饰器
|
||||||
|
|
||||||
|
假设有一个函数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_agent_count():
|
||||||
|
return 3
|
||||||
|
```
|
||||||
|
|
||||||
|
可以手动包装:
|
||||||
|
|
||||||
|
```python
|
||||||
|
get_agent_count = log_call(get_agent_count)
|
||||||
|
```
|
||||||
|
|
||||||
|
等号右边把原函数传给装饰器,装饰器返回 `wrapper`;等号左边再让原名称指向包装后的函数。
|
||||||
|
|
||||||
|
以后调用 `get_agent_count()`,实际先进入 `wrapper()`,再由它调用原函数。
|
||||||
|
|
||||||
|
## 八、使用 `@` 语法
|
||||||
|
|
||||||
|
Python 提供了更清楚的写法:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@log_call
|
||||||
|
def get_agent_count():
|
||||||
|
return 3
|
||||||
|
```
|
||||||
|
|
||||||
|
它与下面的手动写法表达相同含义:
|
||||||
|
|
||||||
|
```python
|
||||||
|
get_agent_count = log_call(get_agent_count)
|
||||||
|
```
|
||||||
|
|
||||||
|
`@log_call` 必须紧挨着函数定义的上一行。
|
||||||
|
|
||||||
|
## 九、转发不同参数
|
||||||
|
|
||||||
|
如果 `wrapper()` 不接收参数,被装饰函数也就无法正常接收参数。为了适应不同函数,可以使用之前学过的 `*args` 和 `**kwargs`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def log_call(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
print(f"调用函数:{func.__name__}")
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
- `*args` 收集并转发位置参数;
|
||||||
|
- `**kwargs` 收集并转发关键字参数;
|
||||||
|
- `func.__name__` 是原函数名称。
|
||||||
|
|
||||||
|
这使装饰器可以包装参数数量不同的函数。
|
||||||
|
|
||||||
|
## 十、不要丢失原函数返回值
|
||||||
|
|
||||||
|
错误写法:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
func(*args, **kwargs)
|
||||||
|
```
|
||||||
|
|
||||||
|
原函数虽然被调用,但结果没有返回,调用者最终得到 `None`。
|
||||||
|
|
||||||
|
正确过程:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
```
|
||||||
|
|
||||||
|
装饰器可以增加行为,但不应无意中改变原函数正常的返回结果。
|
||||||
|
|
||||||
|
## 十一、使用 `functools.wraps`
|
||||||
|
|
||||||
|
包装后,函数名称默认可能变成 `wrapper`。这会影响调试、日志和帮助信息。
|
||||||
|
|
||||||
|
Python 标准库的 `functools` 模块提供 `wraps`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
|
def log_call(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
`@wraps(func)` 会帮助包装函数保留原函数的名称和说明文字。它本身也是装饰器;本课先掌握固定写法。
|
||||||
|
|
||||||
|
## 十二、调用前进行条件检查
|
||||||
|
|
||||||
|
装饰器可以决定是否调用原函数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def require_enabled(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(agent, *args, **kwargs):
|
||||||
|
if not agent.get("enabled", False):
|
||||||
|
print("Agent 已停用,不能执行任务。")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return func(agent, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 Agent 已停用,`wrapper` 提前返回,原函数不会执行;如果已启用,参数会继续转交给原函数。
|
||||||
|
|
||||||
|
## 十三、完整示例
|
||||||
|
|
||||||
|
示例文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
02_python进阶/2_6_装饰器/decorator_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
示例演示:
|
||||||
|
|
||||||
|
1. 使用 `@log_operation` 统一增加开始和结束日志;
|
||||||
|
2. 正确转发 Agent 参数;
|
||||||
|
3. 保留工具数量返回值;
|
||||||
|
4. 使用 `@require_enabled` 阻止停用 Agent 执行任务;
|
||||||
|
5. 使用 `@wraps` 保留原函数名称。
|
||||||
|
|
||||||
|
## 十四、运行方法
|
||||||
|
|
||||||
|
在项目根目录运行示例:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_6_装饰器\decorator_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
完成练习后运行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_6_装饰器\practice.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十五、预期结果
|
||||||
|
|
||||||
|
```text
|
||||||
|
一、统一记录操作日志
|
||||||
|
开始执行:count_tools
|
||||||
|
执行结束:count_tools
|
||||||
|
工具数量:2
|
||||||
|
==============================
|
||||||
|
二、执行前检查 Agent 状态
|
||||||
|
聊天助手 当前已停用,无法执行操作。
|
||||||
|
启用 Agent 的结果:代码助手 正在执行任务:检查代码
|
||||||
|
停用 Agent 的结果:None
|
||||||
|
==============================
|
||||||
|
三、检查函数名称
|
||||||
|
被装饰后的函数名:count_tools
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十六、常见错误
|
||||||
|
|
||||||
|
### 16.1 把函数赋值写成函数调用
|
||||||
|
|
||||||
|
```python
|
||||||
|
saved_function = greet_agent # 保存函数
|
||||||
|
saved_result = greet_agent("代码助手") # 保存结果
|
||||||
|
```
|
||||||
|
|
||||||
|
括号会立即调用函数,两种写法含义不同。
|
||||||
|
|
||||||
|
### 16.2 返回 `wrapper()`
|
||||||
|
|
||||||
|
装饰器最后应返回函数本身:
|
||||||
|
|
||||||
|
```python
|
||||||
|
return wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
写成 `return wrapper()` 会在装饰阶段提前调用。
|
||||||
|
|
||||||
|
### 16.3 忘记调用原函数
|
||||||
|
|
||||||
|
如果 `wrapper` 只输出日志却没有 `func(...)`,原业务功能就不会执行。
|
||||||
|
|
||||||
|
### 16.4 忘记返回原函数结果
|
||||||
|
|
||||||
|
需要使用 `result = func(...)` 和 `return result`,否则调用者可能得到 `None`。
|
||||||
|
|
||||||
|
### 16.5 参数没有继续传递
|
||||||
|
|
||||||
|
包装函数接收到 `*args` 和 `**kwargs` 后,还必须通过 `func(*args, **kwargs)` 转交。
|
||||||
|
|
||||||
|
### 16.6 忘记使用 `@wraps`
|
||||||
|
|
||||||
|
程序可能仍能运行,但函数名称会变成 `wrapper`,不利于日志和调试。
|
||||||
|
|
||||||
|
### 16.7 把所有逻辑都写进装饰器
|
||||||
|
|
||||||
|
装饰器适合通用的附加行为。具体业务逻辑仍应留在原函数中,保持职责清楚。
|
||||||
|
|
||||||
|
## 十七、课堂练习
|
||||||
|
|
||||||
|
打开 `practice.py`,依次完成:
|
||||||
|
|
||||||
|
1. 把函数本身赋值给另一个变量;
|
||||||
|
2. 编写并使用日志装饰器;
|
||||||
|
3. 检查 `@wraps` 是否保留函数名称;
|
||||||
|
4. 编写 Agent 启用状态装饰器;
|
||||||
|
5. 验证停用 Agent 不会执行原函数;
|
||||||
|
6. 在 `main()` 中输出正常和拦截结果。
|
||||||
|
|
||||||
|
## 十八、参考答案
|
||||||
|
|
||||||
|
参考答案暂不写入练习文件。完成后,我会检查装饰器是否真正调用原函数、是否保留参数和返回值,以及停用分支是否正确阻止原函数执行。
|
||||||
|
|
||||||
|
## 十九、本课小结
|
||||||
|
|
||||||
|
- 函数可以赋值给变量,也可以传入和返回;
|
||||||
|
- 装饰器接收原函数并返回包装函数;
|
||||||
|
- `@decorator` 是手动重新赋值的简化语法;
|
||||||
|
- `wrapper` 在调用原函数前后增加统一行为;
|
||||||
|
- `*args` 和 `**kwargs` 可以转发不同参数;
|
||||||
|
- 包装函数应保留原函数返回值;
|
||||||
|
- `@wraps(func)` 可以保留原函数名称等信息;
|
||||||
|
- 装饰器可以在调用前检查条件并阻止无效操作。
|
||||||
|
|
||||||
|
## 二十、验收标准
|
||||||
|
|
||||||
|
- 能解释函数名加括号与不加括号的区别;
|
||||||
|
- 能说明装饰器接收和返回的内容;
|
||||||
|
- 能手动完成一次函数包装;
|
||||||
|
- 能使用 `@` 语法应用装饰器;
|
||||||
|
- `wrapper` 能转发位置参数和关键字参数;
|
||||||
|
- 原函数返回值不会丢失;
|
||||||
|
- `@wraps` 能保留原函数名称;
|
||||||
|
- 启用 Agent 可以执行任务;
|
||||||
|
- 停用 Agent 会被阻止并返回 `None`;
|
||||||
|
- 装饰器只负责通用附加行为,原函数保留具体业务逻辑。
|
||||||
79
02_python进阶/2_6_装饰器/decorator_example.py
Normal file
79
02_python进阶/2_6_装饰器/decorator_example.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# 第 2-6 课完整示例:装饰器
|
||||||
|
#
|
||||||
|
# 装饰器可以在不修改原函数主体的情况下,统一增加额外行为。
|
||||||
|
# 本示例为 Agent 操作增加开始、结束日志,并保留参数和返回值。
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
|
def log_operation(func):
|
||||||
|
"""为函数增加开始和结束日志。"""
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
# func.__name__ 表示被装饰函数原来的名称。
|
||||||
|
print(f"开始执行:{func.__name__}")
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
print(f"执行结束:{func.__name__}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def require_enabled(func):
|
||||||
|
"""只允许对处于启用状态的 Agent 执行操作。"""
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(agent, *args, **kwargs):
|
||||||
|
if not agent.get("enabled", False):
|
||||||
|
print(f"{agent['name']} 当前已停用,无法执行操作。")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return func(agent, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@log_operation
|
||||||
|
def count_tools(agent):
|
||||||
|
"""返回 Agent 的工具数量。"""
|
||||||
|
return len(agent.get("tools", []))
|
||||||
|
|
||||||
|
|
||||||
|
@require_enabled
|
||||||
|
def run_agent(agent, task_name):
|
||||||
|
"""返回 Agent 执行任务时的说明文字。"""
|
||||||
|
return f"{agent['name']} 正在执行任务:{task_name}"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""演示装饰器如何增强函数并保留返回值。"""
|
||||||
|
code_agent = {
|
||||||
|
"name": "代码助手",
|
||||||
|
"enabled": True,
|
||||||
|
"tools": ["搜索", "终端"],
|
||||||
|
}
|
||||||
|
chat_agent = {
|
||||||
|
"name": "聊天助手",
|
||||||
|
"enabled": False,
|
||||||
|
"tools": ["搜索"],
|
||||||
|
}
|
||||||
|
|
||||||
|
print("一、统一记录操作日志")
|
||||||
|
tool_count = count_tools(code_agent)
|
||||||
|
print(f"工具数量:{tool_count}")
|
||||||
|
|
||||||
|
print("=" * 30)
|
||||||
|
print("二、执行前检查 Agent 状态")
|
||||||
|
enabled_result = run_agent(code_agent, "检查代码")
|
||||||
|
disabled_result = run_agent(chat_agent, "整理对话")
|
||||||
|
print(f"启用 Agent 的结果:{enabled_result}")
|
||||||
|
print(f"停用 Agent 的结果:{disabled_result}")
|
||||||
|
|
||||||
|
print("=" * 30)
|
||||||
|
print("三、检查函数名称")
|
||||||
|
print(f"被装饰后的函数名:{count_tools.__name__}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
83
02_python进阶/2_6_装饰器/practice.py
Normal file
83
02_python进阶/2_6_装饰器/practice.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# 第 2-6 课课堂练习:装饰器
|
||||||
|
#
|
||||||
|
# 请先阅读讲义并运行完整示例,再按照题目顺序完成。
|
||||||
|
# 装饰器必须调用原函数并保留原函数的返回值。
|
||||||
|
# 不要删除题目、测试数据、预期结果和自查注释。
|
||||||
|
|
||||||
|
# 第一部分:理解函数可以赋值给变量
|
||||||
|
# 1. 定义 greet_agent(name) 函数。
|
||||||
|
# 2. 函数 return f"你好,{name}。",不要在函数内部 print()。
|
||||||
|
# 3. 把 greet_agent 函数本身赋值给变量 greeting_function,赋值时不要写括号。
|
||||||
|
# 4. 调用 greeting_function("代码助手"),保存并输出返回值。
|
||||||
|
# 5. 预期结果:“你好,代码助手。”。
|
||||||
|
|
||||||
|
# greeting_function = greet_agent
|
||||||
|
# print(greeting_function("代码助手"))
|
||||||
|
|
||||||
|
# 第二部分:定义 log_call(func) 装饰器
|
||||||
|
# 1. 在 log_call() 内定义 wrapper(*args, **kwargs)。
|
||||||
|
# 2. 在 wrapper 上方使用 @wraps(func)。
|
||||||
|
# 3. wrapper 先输出 f"调用函数:{func.__name__}"。
|
||||||
|
# 4. 使用 func(*args, **kwargs) 调用原函数,并把返回值保存为 result。
|
||||||
|
# 5. wrapper return result,不能丢失原函数返回值。
|
||||||
|
# 6. log_call() return wrapper,注意不要写成 wrapper()。
|
||||||
|
|
||||||
|
# 第三部分:使用 @log_call
|
||||||
|
# 1. 定义 get_agent_name(agent) 函数。
|
||||||
|
# 2. 在函数上方写 @log_call。
|
||||||
|
# 3. 函数 return agent["name"]。
|
||||||
|
# 4. 调用 get_agent_name(enabled_agent),保存并输出结果。
|
||||||
|
# 5. 预期先输出“调用函数:get_agent_name”,再得到“代码助手”。
|
||||||
|
# 6. 检查 get_agent_name.__name__,预期仍是 "get_agent_name"。
|
||||||
|
|
||||||
|
# print(get_agent_name(enabled_agent))
|
||||||
|
# print(get_agent_name.__name__)
|
||||||
|
|
||||||
|
# 第四部分:定义 require_enabled(func) 装饰器
|
||||||
|
# 1. 在 require_enabled() 内定义 wrapper(agent, *args, **kwargs)。
|
||||||
|
# 2. 在 wrapper 上方使用 @wraps(func)。
|
||||||
|
# 3. 如果 agent.get("enabled", False) 为 False:
|
||||||
|
# 输出“Agent 已停用,不能执行任务。”并 return None。
|
||||||
|
# 4. 如果 Agent 已启用,调用 func(agent, *args, **kwargs) 并 return 原结果。
|
||||||
|
# 5. require_enabled() return wrapper。
|
||||||
|
|
||||||
|
# 第五部分:使用 @require_enabled
|
||||||
|
# 1. 定义 run_task(agent, task_name) 函数。
|
||||||
|
# 2. 在函数上方写 @require_enabled。
|
||||||
|
# 3. 函数 return f"{agent['name']} 正在执行:{task_name}"。
|
||||||
|
# 4. 传入 enabled_agent 和 "检查代码",预期返回:
|
||||||
|
# “代码助手 正在执行:检查代码”。
|
||||||
|
# 5. 传入 disabled_agent 和 "整理对话",预期输出停用提示并返回 None。
|
||||||
|
|
||||||
|
# print(run_task(enabled_agent ,"检查代码"))
|
||||||
|
# print(run_task(disabled_agent ,"整理对话"))
|
||||||
|
|
||||||
|
# 第六部分:定义 main() 函数
|
||||||
|
# 1. 完成第一部分的函数赋值实验,并输出问候结果。
|
||||||
|
# 2. 调用 get_agent_name(enabled_agent),保存并输出返回值。
|
||||||
|
# 3. 输出 get_agent_name.__name__,确认 @wraps 保留了原函数名。
|
||||||
|
# 4. 分别使用启用和停用 Agent 调用 run_task()。
|
||||||
|
# 5. 保存并输出两个调用结果,停用 Agent 的结果应为 None。
|
||||||
|
# 6. 添加程序入口判断,直接运行本文件时调用 main()。
|
||||||
|
|
||||||
|
# 最终验收测试:
|
||||||
|
# 1. greeting_function 与 greet_agent 指向同一个函数;
|
||||||
|
# 2. greeting_function("代码助手") 返回正确文字;
|
||||||
|
# 3. get_agent_name() 调用前输出函数名称;
|
||||||
|
# 4. get_agent_name(enabled_agent) 返回“代码助手”;
|
||||||
|
# 5. get_agent_name.__name__ 仍是 "get_agent_name";
|
||||||
|
# 6. run_task(enabled_agent, "检查代码") 返回正确文字;
|
||||||
|
# 7. run_task(disabled_agent, "整理对话") 返回 None;
|
||||||
|
# 8. 停用 Agent 不会执行原 run_task() 的函数主体;
|
||||||
|
# 9. 两个装饰器都能接收参数并保留原函数返回值。
|
||||||
|
|
||||||
|
|
||||||
|
# 完成后自查:
|
||||||
|
# 1. 是否理解函数名不加括号表示函数本身;
|
||||||
|
# 2. 是否理解函数名加括号表示立即调用函数;
|
||||||
|
# 3. 装饰器是否接收原函数并返回 wrapper;
|
||||||
|
# 4. wrapper 是否调用并返回原函数结果;
|
||||||
|
# 5. 是否使用 *args 和 **kwargs 转发参数;
|
||||||
|
# 6. 是否使用 @wraps(func) 保留原函数信息;
|
||||||
|
# 7. 是否能解释 @log_call 与手动重新赋值的关系;
|
||||||
|
# 8. 是否保留了完整题目和验收说明。
|
||||||
383
02_python进阶/2_7_类型注解/README.md
Normal file
383
02_python进阶/2_7_类型注解/README.md
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
# 第 2-7 课:类型注解
|
||||||
|
|
||||||
|
## 一、本课目标
|
||||||
|
|
||||||
|
完成本课后,你将能够:
|
||||||
|
|
||||||
|
1. 解释类型注解是什么,以及它能解决什么问题;
|
||||||
|
2. 为变量、函数参数和返回值添加类型注解;
|
||||||
|
3. 标注 `str`、`int`、`float` 和 `bool`;
|
||||||
|
4. 标注字符串列表与字符串字典;
|
||||||
|
5. 使用 `类型 | None` 表示结果可能不存在;
|
||||||
|
6. 使用 `-> None` 标注只执行操作、不返回业务结果的函数;
|
||||||
|
7. 理解类型注解通常不会在运行时自动强制检查数据。
|
||||||
|
|
||||||
|
## 二、前置知识
|
||||||
|
|
||||||
|
学习本课前,需要掌握:
|
||||||
|
|
||||||
|
- 变量和基本数据类型;
|
||||||
|
- 列表与字典;
|
||||||
|
- 函数、参数与返回值;
|
||||||
|
- 条件判断;
|
||||||
|
- `None` 的基本含义。
|
||||||
|
|
||||||
|
## 三、为什么需要类型注解
|
||||||
|
|
||||||
|
观察下面的函数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def format_status(name, enabled):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
只看函数定义,我们无法立即确定:
|
||||||
|
|
||||||
|
- `name` 应该传字符串还是字典;
|
||||||
|
- `enabled` 应该传布尔值还是文字;
|
||||||
|
- 函数最后返回什么类型。
|
||||||
|
|
||||||
|
添加类型注解(Type Hint):
|
||||||
|
|
||||||
|
```python
|
||||||
|
def format_status(name: str, enabled: bool) -> str:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
现在可以看出:
|
||||||
|
|
||||||
|
- `name` 预期是字符串;
|
||||||
|
- `enabled` 预期是布尔值;
|
||||||
|
- `-> str` 表示预期返回字符串。
|
||||||
|
|
||||||
|
类型注解的主要作用是帮助人和工具理解代码。
|
||||||
|
|
||||||
|
## 四、类型注解不会自动改变数据
|
||||||
|
|
||||||
|
```python
|
||||||
|
age: int = 18
|
||||||
|
```
|
||||||
|
|
||||||
|
`int` 说明变量预期保存整数,但它不会执行类型转换。下面两段代码不同:
|
||||||
|
|
||||||
|
```python
|
||||||
|
age: int = "18" # 只是写了注解,值仍然是字符串
|
||||||
|
age = int("18") # 真正把字符串转换为整数
|
||||||
|
```
|
||||||
|
|
||||||
|
Python 通常也不会因为注解与实际值不一致而立即阻止程序运行。编辑器或静态类型检查工具可以提前提示问题。
|
||||||
|
|
||||||
|
静态类型检查(Static Type Checking)表示在不真正执行全部业务代码的情况下,根据注解检查可能的类型错误。
|
||||||
|
|
||||||
|
## 五、变量类型注解
|
||||||
|
|
||||||
|
基本格式:
|
||||||
|
|
||||||
|
```python
|
||||||
|
变量名: 类型 = 值
|
||||||
|
```
|
||||||
|
|
||||||
|
常见示例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
agent_name: str = "代码助手"
|
||||||
|
tool_count: int = 2
|
||||||
|
completion_rate: float = 0.75
|
||||||
|
enabled: bool = True
|
||||||
|
```
|
||||||
|
|
||||||
|
冒号后面是预期类型,等号后面仍然是实际值。
|
||||||
|
|
||||||
|
简单变量的类型通常很容易从值中看出,因此不必强迫每个局部变量都写注解。类型不明显或需要强调时再添加。
|
||||||
|
|
||||||
|
## 六、参数和返回值注解
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_status_text(enabled: bool) -> str:
|
||||||
|
return "启用" if enabled else "停用"
|
||||||
|
```
|
||||||
|
|
||||||
|
- `enabled: bool` 是参数注解;
|
||||||
|
- `-> str` 是返回值注解;
|
||||||
|
- 注解不影响函数正常调用方式。
|
||||||
|
|
||||||
|
调用仍然写成:
|
||||||
|
|
||||||
|
```python
|
||||||
|
status_text = get_status_text(True)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 七、常见基本类型
|
||||||
|
|
||||||
|
本课使用以下类型:
|
||||||
|
|
||||||
|
| 注解 | 表示的数据 |
|
||||||
|
|---|---|
|
||||||
|
| `str` | 字符串 |
|
||||||
|
| `int` | 整数 |
|
||||||
|
| `float` | 浮点数 |
|
||||||
|
| `bool` | 布尔值 |
|
||||||
|
| `None` | 没有业务结果 |
|
||||||
|
|
||||||
|
类型名称不需要加引号:
|
||||||
|
|
||||||
|
```python
|
||||||
|
name: str = "代码助手"
|
||||||
|
```
|
||||||
|
|
||||||
|
不要写成:
|
||||||
|
|
||||||
|
```python
|
||||||
|
name: "str" = "代码助手"
|
||||||
|
```
|
||||||
|
|
||||||
|
字符串形式的类型注解有其他用途,本课暂不展开。
|
||||||
|
|
||||||
|
## 八、列表类型注解
|
||||||
|
|
||||||
|
字符串列表:
|
||||||
|
|
||||||
|
```python
|
||||||
|
tools: list[str] = ["搜索", "终端"]
|
||||||
|
```
|
||||||
|
|
||||||
|
可以从外向内理解:
|
||||||
|
|
||||||
|
- `list`:这是列表;
|
||||||
|
- `[str]`:列表中的元素预期是字符串。
|
||||||
|
|
||||||
|
函数示例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def count_tools(tools: list[str]) -> int:
|
||||||
|
return len(tools)
|
||||||
|
```
|
||||||
|
|
||||||
|
返回字符串列表:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_names() -> list[str]:
|
||||||
|
return ["代码助手", "聊天助手"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 九、字典类型注解
|
||||||
|
|
||||||
|
名称到模型的对应关系,可以标注为:
|
||||||
|
|
||||||
|
```python
|
||||||
|
models: dict[str, str] = {
|
||||||
|
"代码助手": "gpt-5",
|
||||||
|
"聊天助手": "o3",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`dict[str, str]` 表示:
|
||||||
|
|
||||||
|
- 第一个 `str`:字典键是字符串;
|
||||||
|
- 第二个 `str`:字典值也是字符串。
|
||||||
|
|
||||||
|
任务列表可能写成:
|
||||||
|
|
||||||
|
```python
|
||||||
|
tasks: list[dict[str, str]] = [
|
||||||
|
{"title": "学习类型注解", "status": "已完成"},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
从外向内理解:这是一个列表,列表中每项是字典,字典的键和值都是字符串。
|
||||||
|
|
||||||
|
## 十、结果可能是 `None`
|
||||||
|
|
||||||
|
查找操作可能找到字符串,也可能找不到:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def find_model(
|
||||||
|
models: dict[str, str],
|
||||||
|
agent_name: str,
|
||||||
|
) -> str | None:
|
||||||
|
return models.get(agent_name)
|
||||||
|
```
|
||||||
|
|
||||||
|
`str | None` 表示返回值有两种可能:
|
||||||
|
|
||||||
|
- 找到时返回 `str`;
|
||||||
|
- 未找到时返回 `None`。
|
||||||
|
|
||||||
|
符号 `|` 可以理解为“或者”。
|
||||||
|
|
||||||
|
调用者应检查:
|
||||||
|
|
||||||
|
```python
|
||||||
|
model_name = find_model(models, "测试助手")
|
||||||
|
|
||||||
|
if model_name is None:
|
||||||
|
print("没有找到模型。")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十一、返回值标注为 `None`
|
||||||
|
|
||||||
|
只负责输出、不返回业务结果的函数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def print_agent(name: str) -> None:
|
||||||
|
print(name)
|
||||||
|
```
|
||||||
|
|
||||||
|
`-> None` 表示调用者不应期待它返回可供后续使用的业务数据。
|
||||||
|
|
||||||
|
程序入口也常写成:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def main() -> None:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十二、类型注解和默认值
|
||||||
|
|
||||||
|
默认值写在类型注解之后:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def create_agent(name: str, enabled: bool = True) -> str:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
阅读顺序:
|
||||||
|
|
||||||
|
- 参数名是 `enabled`;
|
||||||
|
- 类型是 `bool`;
|
||||||
|
- 默认值是 `True`。
|
||||||
|
|
||||||
|
不要把类型和默认值的位置写反。
|
||||||
|
|
||||||
|
## 十三、类型注解能带来什么
|
||||||
|
|
||||||
|
类型注解可以帮助:
|
||||||
|
|
||||||
|
- 阅读者更快理解参数和返回值;
|
||||||
|
- 编辑器提供更准确的自动补全;
|
||||||
|
- 编辑器提前提示可能的类型错误;
|
||||||
|
- 重构代码时发现受影响的位置;
|
||||||
|
- 静态类型检查工具检查大型项目。
|
||||||
|
|
||||||
|
类型注解不能代替:
|
||||||
|
|
||||||
|
- 运行时数据校验;
|
||||||
|
- `try...except` 异常处理;
|
||||||
|
- 业务规则判断;
|
||||||
|
- 单元测试。
|
||||||
|
|
||||||
|
例如,Web 接口收到的外部数据仍然需要真实校验,不能只依靠注解。
|
||||||
|
|
||||||
|
## 十四、完整示例
|
||||||
|
|
||||||
|
示例文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
02_python进阶/2_7_类型注解/type_annotation_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
示例会演示:
|
||||||
|
|
||||||
|
1. 字符串和布尔参数注解;
|
||||||
|
2. 字符串列表和字符串字典;
|
||||||
|
3. 整数返回值;
|
||||||
|
4. `str | None`;
|
||||||
|
5. 只输出函数的 `-> None`。
|
||||||
|
|
||||||
|
## 十五、运行方法
|
||||||
|
|
||||||
|
在项目根目录运行示例:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_7_类型注解\type_annotation_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
完成练习后运行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_7_类型注解\practice.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十六、预期结果
|
||||||
|
|
||||||
|
```text
|
||||||
|
代码助手|状态:启用
|
||||||
|
模型:gpt-5
|
||||||
|
不存在的模型:None
|
||||||
|
Agent 名称:代码助手
|
||||||
|
工具数量:2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十七、常见错误
|
||||||
|
|
||||||
|
### 17.1 把注解当作类型转换
|
||||||
|
|
||||||
|
```python
|
||||||
|
count: int = "2"
|
||||||
|
```
|
||||||
|
|
||||||
|
实际值仍然是字符串。需要转换时必须使用 `int("2")`。
|
||||||
|
|
||||||
|
### 17.2 忘记返回值箭头
|
||||||
|
|
||||||
|
参数使用冒号,返回值使用 `->`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def count_tools(tools: list[str]) -> int:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 17.3 列表没有写元素类型
|
||||||
|
|
||||||
|
只写 `list` 无法说明元素是什么。本课优先写成 `list[str]`。
|
||||||
|
|
||||||
|
### 17.4 混淆字典键和值的类型
|
||||||
|
|
||||||
|
`dict[str, int]` 表示字符串键和整数值,前后顺序不能颠倒。
|
||||||
|
|
||||||
|
### 17.5 可能返回 `None` 却只标注字符串
|
||||||
|
|
||||||
|
如果函数可能找不到结果,应标注 `str | None`,提醒调用者处理空结果。
|
||||||
|
|
||||||
|
### 17.6 认为有注解就不需要测试
|
||||||
|
|
||||||
|
类型正确不代表业务结果正确。边界条件、异常和实际输出仍需验证。
|
||||||
|
|
||||||
|
## 十八、课堂练习
|
||||||
|
|
||||||
|
打开 `practice.py`,依次完成:
|
||||||
|
|
||||||
|
1. 为任务状态格式化函数添加基本类型;
|
||||||
|
2. 创建并返回字符串字典;
|
||||||
|
3. 处理任务字典列表;
|
||||||
|
4. 使用 `float | None` 表示两种完成率结果;
|
||||||
|
5. 使用 `-> None` 标注输出函数和主函数;
|
||||||
|
6. 运行正常与零任务数用例。
|
||||||
|
|
||||||
|
## 十九、参考答案
|
||||||
|
|
||||||
|
参考答案暂不写入练习文件。完成后,我会验证函数行为与 `__annotations__` 中的实际注解,并检查注解是否与真实返回值一致。
|
||||||
|
|
||||||
|
## 二十、本课小结
|
||||||
|
|
||||||
|
- 类型注解说明代码预期使用的类型;
|
||||||
|
- 参数类型写在冒号后,返回类型写在 `->` 后;
|
||||||
|
- `list[str]` 表示字符串列表;
|
||||||
|
- `dict[str, str]` 表示字符串键和字符串值;
|
||||||
|
- `str | None` 表示可能返回字符串或 `None`;
|
||||||
|
- 只执行操作的函数可以标注 `-> None`;
|
||||||
|
- 类型注解不会自动转换或验证运行时数据;
|
||||||
|
- 类型注解提高可读性,但不能代替校验、异常处理和测试。
|
||||||
|
|
||||||
|
## 二十一、验收标准
|
||||||
|
|
||||||
|
- 能解释类型注解的作用;
|
||||||
|
- 能为基本类型参数和返回值添加注解;
|
||||||
|
- 能正确标注字符串列表;
|
||||||
|
- 能正确标注字符串字典;
|
||||||
|
- 能看懂嵌套的 `list[dict[str, str]]`;
|
||||||
|
- 能使用 `float | None` 表达两种结果;
|
||||||
|
- 能使用 `-> None` 标注输出函数;
|
||||||
|
- 能说明注解与类型转换的区别;
|
||||||
|
- 能说明类型注解通常不会自动强制检查运行时数据;
|
||||||
|
- 所有注解与函数真实返回值保持一致。
|
||||||
84
02_python进阶/2_7_类型注解/practice.py
Normal file
84
02_python进阶/2_7_类型注解/practice.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# 第 2-7 课课堂练习:类型注解
|
||||||
|
#
|
||||||
|
# 请先阅读讲义并运行完整示例,再按照题目顺序完成。
|
||||||
|
# 每个函数都需要写参数类型和返回值类型。
|
||||||
|
# 不要删除题目、测试数据、预期结果和自查注释。
|
||||||
|
|
||||||
|
|
||||||
|
# 第一部分:定义 format_task_status(title, completed) 函数
|
||||||
|
# 1. 参数 title 标注为 str。
|
||||||
|
# 2. 参数 completed 标注为 bool。
|
||||||
|
# 3. 返回值标注为 str。
|
||||||
|
# 4. completed 为 True 时状态文字是“已完成”,否则是“未完成”。
|
||||||
|
# 5. return f"{title}|状态:{status_text}"。
|
||||||
|
# 6. 传入“学习类型注解”和 True,预期返回:
|
||||||
|
# “学习类型注解|状态:已完成”。
|
||||||
|
|
||||||
|
# print(format_task_status("学习类型注解", True))
|
||||||
|
|
||||||
|
# 第二部分:定义 build_task(title, status) 函数
|
||||||
|
# 1. 两个参数都标注为 str。
|
||||||
|
# 2. 返回值标注为 dict[str, str]。
|
||||||
|
# 3. return 包含 title 和 status 的新字典。
|
||||||
|
# 4. 传入“复习生成器”和“未开始”,预期返回:
|
||||||
|
# {"title": "复习生成器", "status": "未开始"}。
|
||||||
|
|
||||||
|
# print(build_task("复习生成器", "未开始"))
|
||||||
|
|
||||||
|
# 第三部分:定义 get_task_titles(tasks) 函数
|
||||||
|
# 1. 参数标注为 list[dict[str, str]]。
|
||||||
|
# 2. 返回值标注为 list[str]。
|
||||||
|
# 3. 使用列表推导式取得每个任务的 title。
|
||||||
|
# 4. return 新列表,不要修改原 tasks。
|
||||||
|
# 5. 使用顶部测试数据,预期返回:
|
||||||
|
# ["学习类型注解", "完成课堂练习"]。
|
||||||
|
|
||||||
|
# print(get_task_titles(tasks))
|
||||||
|
|
||||||
|
# 第四部分:定义 calculate_completion_rate(completed_count, total_count) 函数
|
||||||
|
# 1. 两个参数都标注为 int。
|
||||||
|
# 2. 返回值标注为 float | None,表示可能返回浮点数,也可能返回 None。
|
||||||
|
# 3. total_count 为 0 时 return None。
|
||||||
|
# 4. 否则 return completed_count / total_count。
|
||||||
|
# 5. 传入 3 和 4,预期返回 0.75。
|
||||||
|
# 6. 传入 0 和 0,预期返回 None,程序不能崩溃。
|
||||||
|
|
||||||
|
# print(calculate_completion_rate(3,4))
|
||||||
|
# print(calculate_completion_rate(0,0))
|
||||||
|
|
||||||
|
# 第五部分:定义 print_task(task) 函数
|
||||||
|
# 1. 参数标注为 dict[str, str]。
|
||||||
|
# 2. 返回值标注为 None,因为本函数只输出,不返回业务结果。
|
||||||
|
# 3. 按“任务:标题|状态:状态文字”的格式输出。
|
||||||
|
# 4. 传入第一个测试任务,预期输出:
|
||||||
|
# “任务:学习类型注解|状态:已完成”。
|
||||||
|
|
||||||
|
# print_task(tasks[0])
|
||||||
|
|
||||||
|
# 第六部分:定义 main() 函数
|
||||||
|
# 1. 返回值标注为 None。
|
||||||
|
# 2. 依次调用前面五个函数,并保存需要使用的返回值。
|
||||||
|
# 3. 输出格式化状态、新任务字典、任务标题列表和两种完成率。
|
||||||
|
# 4. 调用 print_task(tasks[0]) 输出第一个任务。
|
||||||
|
# 5. 添加程序入口判断,直接运行本文件时调用 main()。
|
||||||
|
|
||||||
|
# 最终验收测试:
|
||||||
|
# 1. format_task_status() 的参数和返回值注解正确;
|
||||||
|
# 2. build_task() 返回正确字典,并标注 dict[str, str];
|
||||||
|
# 3. get_task_titles() 返回正确列表,并标注 list[str];
|
||||||
|
# 4. calculate_completion_rate(3, 4) 返回 0.75;
|
||||||
|
# 5. calculate_completion_rate(0, 0) 返回 None;
|
||||||
|
# 6. 完成率返回类型标注为 float | None;
|
||||||
|
# 7. print_task() 和 main() 的返回值标注为 None;
|
||||||
|
# 8. 原始 tasks 没有被修改;
|
||||||
|
# 9. 直接运行程序时,所有结果均正确输出。
|
||||||
|
|
||||||
|
|
||||||
|
# 完成后自查:
|
||||||
|
# 1. 是否知道冒号后写参数或变量类型;
|
||||||
|
# 2. 是否知道 -> 后写函数返回值类型;
|
||||||
|
# 3. 是否能区分 list[str] 和 dict[str, str];
|
||||||
|
# 4. 是否理解 float | None 表示两种可能结果;
|
||||||
|
# 5. 是否知道只输出的函数返回值标注为 None;
|
||||||
|
# 6. 是否理解类型注解通常不会自动检查运行时数据;
|
||||||
|
# 7. 是否保留了完整题目和验收说明。
|
||||||
53
02_python进阶/2_7_类型注解/type_annotation_example.py
Normal file
53
02_python进阶/2_7_类型注解/type_annotation_example.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# 第 2-7 课完整示例:类型注解
|
||||||
|
#
|
||||||
|
# 类型注解用于说明变量、参数和返回值预期使用的类型。
|
||||||
|
# Python 通常不会在运行时自动强制检查这些注解。
|
||||||
|
|
||||||
|
|
||||||
|
def format_agent_status(name: str, enabled: bool) -> str:
|
||||||
|
"""根据名称和启用状态返回中文说明。"""
|
||||||
|
status_text = "启用" if enabled else "停用"
|
||||||
|
return f"{name}|状态:{status_text}"
|
||||||
|
|
||||||
|
|
||||||
|
def count_tools(tools: list[str]) -> int:
|
||||||
|
"""返回工具名称列表中的元素数量。"""
|
||||||
|
return len(tools)
|
||||||
|
|
||||||
|
|
||||||
|
def find_agent_model(
|
||||||
|
model_mapping: dict[str, str],
|
||||||
|
agent_name: str,
|
||||||
|
) -> str | None:
|
||||||
|
"""返回指定 Agent 的模型,找不到时返回 None。"""
|
||||||
|
return model_mapping.get(agent_name)
|
||||||
|
|
||||||
|
|
||||||
|
def print_agent_report(name: str, tools: list[str]) -> None:
|
||||||
|
"""输出 Agent 报告,本函数不返回业务结果。"""
|
||||||
|
print(f"Agent 名称:{name}")
|
||||||
|
print(f"工具数量:{count_tools(tools)}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""准备带类型注解的数据并调用示例函数。"""
|
||||||
|
agent_name: str = "代码助手"
|
||||||
|
enabled: bool = True
|
||||||
|
tools: list[str] = ["搜索", "终端"]
|
||||||
|
model_mapping: dict[str, str] = {
|
||||||
|
"代码助手": "gpt-5",
|
||||||
|
"聊天助手": "o3",
|
||||||
|
}
|
||||||
|
|
||||||
|
status_text = format_agent_status(agent_name, enabled)
|
||||||
|
model_name = find_agent_model(model_mapping, agent_name)
|
||||||
|
missing_model = find_agent_model(model_mapping, "测试助手")
|
||||||
|
|
||||||
|
print(status_text)
|
||||||
|
print(f"模型:{model_name}")
|
||||||
|
print(f"不存在的模型:{missing_model}")
|
||||||
|
print_agent_report(agent_name, tools)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
410
02_python进阶/2_8_虚拟环境与依赖管理/README.md
Normal file
410
02_python进阶/2_8_虚拟环境与依赖管理/README.md
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
# 第 2-8 课:虚拟环境与依赖管理
|
||||||
|
|
||||||
|
## 一、本课目标
|
||||||
|
|
||||||
|
完成本课后,你将能够:
|
||||||
|
|
||||||
|
1. 解释虚拟环境是什么,以及为什么需要它;
|
||||||
|
2. 在 Windows PowerShell 中创建、激活和退出虚拟环境;
|
||||||
|
3. 确认命令实际使用的是哪个 Python 解释器;
|
||||||
|
4. 使用 `python -m pip` 查看、安装和卸载第三方依赖;
|
||||||
|
5. 使用 `requirements.txt` 记录与恢复项目依赖;
|
||||||
|
6. 理解版本固定的基本作用;
|
||||||
|
7. 知道 `.venv` 不应提交到 Git。
|
||||||
|
|
||||||
|
## 二、前置知识
|
||||||
|
|
||||||
|
学习本课前,需要掌握:
|
||||||
|
|
||||||
|
- 在 PowerShell 中进入目录并运行 Python 文件;
|
||||||
|
- 模块与导入;
|
||||||
|
- 文件和路径;
|
||||||
|
- 基础异常处理;
|
||||||
|
- 类型注解。
|
||||||
|
|
||||||
|
## 三、什么是第三方依赖
|
||||||
|
|
||||||
|
Python 自带的模块组成标准库(Standard Library),例如 `pathlib`、`sys` 和 `platform`,使用它们通常不需要安装额外内容。
|
||||||
|
|
||||||
|
第三方依赖(Third-Party Dependency)是其他开发者发布、项目需要额外安装的软件包。例如以后 Web 阶段会使用 FastAPI、Django 等框架。
|
||||||
|
|
||||||
|
安装依赖后,我们可以在代码中导入它:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import package_name
|
||||||
|
```
|
||||||
|
|
||||||
|
这里的 `package_name` 只是格式示例,不要求本课安装这个不存在的软件包。
|
||||||
|
|
||||||
|
## 四、为什么不能把所有依赖都装在同一个环境
|
||||||
|
|
||||||
|
假设有两个项目:
|
||||||
|
|
||||||
|
- 项目 A 需要某个软件包的旧版本;
|
||||||
|
- 项目 B 需要同一个软件包的新版本。
|
||||||
|
|
||||||
|
如果两个项目共用一个 Python 环境,升级项目 B 的依赖可能导致项目 A 无法运行。
|
||||||
|
|
||||||
|
还可能遇到:
|
||||||
|
|
||||||
|
- 不知道某个依赖属于哪个项目;
|
||||||
|
- 删除一个依赖时误伤其他项目;
|
||||||
|
- 自己的机器能运行,换一台机器却缺少依赖;
|
||||||
|
- 项目依赖越来越杂乱。
|
||||||
|
|
||||||
|
## 五、什么是虚拟环境
|
||||||
|
|
||||||
|
虚拟环境(Virtual Environment)是为一个项目准备的独立 Python 运行环境。它拥有自己的 Python 入口和依赖安装位置。
|
||||||
|
|
||||||
|
可以把它理解为每个项目自己的“工具箱”:
|
||||||
|
|
||||||
|
```text
|
||||||
|
项目 A → 自己的虚拟环境 → 自己的依赖
|
||||||
|
项目 B → 自己的虚拟环境 → 自己的依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
虚拟环境不会复制整个操作系统,也不会删除系统 Python。它只是隔离 Python 解释器入口和软件包目录。
|
||||||
|
|
||||||
|
## 六、创建虚拟环境
|
||||||
|
|
||||||
|
本项目统一把虚拟环境放在项目根目录的 `.venv` 中。
|
||||||
|
|
||||||
|
先在 PowerShell 进入项目根目录:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd D:\Code\Python
|
||||||
|
```
|
||||||
|
|
||||||
|
创建环境:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m venv .venv
|
||||||
|
```
|
||||||
|
|
||||||
|
逐段理解:
|
||||||
|
|
||||||
|
- `python`:使用当前 Python;
|
||||||
|
- `-m venv`:运行标准库中的 `venv` 模块;
|
||||||
|
- `.venv`:创建的环境目录名称。
|
||||||
|
|
||||||
|
创建可能需要几秒钟。成功后根目录会出现 `.venv`,但项目根目录的 `.gitignore` 已忽略它。
|
||||||
|
|
||||||
|
## 七、激活虚拟环境
|
||||||
|
|
||||||
|
PowerShell 中执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
激活后,命令提示符前通常会出现:
|
||||||
|
|
||||||
|
```text
|
||||||
|
(.venv)
|
||||||
|
```
|
||||||
|
|
||||||
|
激活的作用主要是临时调整当前终端的命令查找顺序,让 `python` 和 `pip` 优先指向 `.venv`。
|
||||||
|
|
||||||
|
激活只影响当前终端窗口。关闭终端后,下次需要重新激活。
|
||||||
|
|
||||||
|
## 八、遇到 PowerShell 脚本限制怎么办
|
||||||
|
|
||||||
|
部分机器可能阻止运行 `Activate.ps1`。本课不要求为了激活环境而修改整台电脑的执行策略。
|
||||||
|
|
||||||
|
即使不激活,也可以直接使用虚拟环境中的 Python:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe --version
|
||||||
|
.\.venv\Scripts\python.exe .\02_python进阶\2_8_虚拟环境与依赖管理\environment_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这种写法目标明确,也能保证使用 `.venv`。如果遇到执行策略错误,保留英文错误原文,再说明你执行的命令,我会帮助判断。
|
||||||
|
|
||||||
|
## 九、确认正在使用哪个 Python
|
||||||
|
|
||||||
|
激活前后都可以执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -c "import sys; print(sys.executable)"
|
||||||
|
```
|
||||||
|
|
||||||
|
激活虚拟环境后,输出路径应指向项目中的 `.venv\Scripts\python.exe`。
|
||||||
|
|
||||||
|
还可以执行本课示例:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_8_虚拟环境与依赖管理\environment_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
示例通过比较:
|
||||||
|
|
||||||
|
```python
|
||||||
|
sys.prefix != sys.base_prefix
|
||||||
|
```
|
||||||
|
|
||||||
|
判断当前是否处于普通 `venv` 虚拟环境。
|
||||||
|
|
||||||
|
## 十、退出虚拟环境
|
||||||
|
|
||||||
|
激活后执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
deactivate
|
||||||
|
```
|
||||||
|
|
||||||
|
退出只会恢复当前终端的命令环境,不会删除 `.venv`。
|
||||||
|
|
||||||
|
以后重新使用时再次运行激活命令即可。
|
||||||
|
|
||||||
|
## 十一、什么是 `pip`
|
||||||
|
|
||||||
|
`pip` 是 Python 常用的软件包安装工具。它可以从软件包仓库下载和管理第三方依赖。
|
||||||
|
|
||||||
|
本课推荐始终使用:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip 命令
|
||||||
|
```
|
||||||
|
|
||||||
|
而不是只写:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip 命令
|
||||||
|
```
|
||||||
|
|
||||||
|
`python -m pip` 能更明确地表示:“使用当前这个 Python 对应的 pip”,减少多个 Python 环境之间装错位置的风险。
|
||||||
|
|
||||||
|
## 十二、查看依赖
|
||||||
|
|
||||||
|
查看 pip 版本和安装位置:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip --version
|
||||||
|
```
|
||||||
|
|
||||||
|
查看当前环境已安装的软件包:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip list
|
||||||
|
```
|
||||||
|
|
||||||
|
查看某个已安装软件包的信息:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip show 软件包名称
|
||||||
|
```
|
||||||
|
|
||||||
|
检查已安装依赖之间是否存在已知的不兼容:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip check
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十三、安装与卸载依赖
|
||||||
|
|
||||||
|
安装命令格式:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip install 软件包名称
|
||||||
|
```
|
||||||
|
|
||||||
|
安装指定版本:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip install 软件包名称==版本号
|
||||||
|
```
|
||||||
|
|
||||||
|
卸载:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip uninstall 软件包名称
|
||||||
|
```
|
||||||
|
|
||||||
|
安装和卸载会修改当前 Python 环境。执行前必须先确认解释器路径,避免改动错误环境。本课不强制安装任何第三方依赖,也不要把示例中的占位名称原样执行。
|
||||||
|
|
||||||
|
## 十四、什么是版本固定
|
||||||
|
|
||||||
|
版本固定(Version Pinning)表示明确记录项目使用的依赖版本:
|
||||||
|
|
||||||
|
```text
|
||||||
|
some-package==1.2.3
|
||||||
|
```
|
||||||
|
|
||||||
|
它可以减少不同机器安装到不同版本而产生的差异。
|
||||||
|
|
||||||
|
需要注意:版本号必须来自项目实际验证过的环境,不要随意照抄教程中的旧版本。以后开始 FastAPI、Django 项目时,会根据实际环境选择并验证版本。
|
||||||
|
|
||||||
|
## 十五、使用 `requirements.txt`
|
||||||
|
|
||||||
|
`requirements.txt` 是常见的依赖清单文件,每行记录一个直接依赖或带版本的依赖:
|
||||||
|
|
||||||
|
```text
|
||||||
|
package-a==1.2.3
|
||||||
|
package-b==4.5.6
|
||||||
|
```
|
||||||
|
|
||||||
|
在另一台机器或新虚拟环境中恢复依赖:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
根据当前环境导出所有已安装包:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip freeze > requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
`pip freeze` 会记录当前环境中的全部软件包,包括间接依赖。导出前要确认环境干净、确实属于当前项目,避免把无关包写入清单。
|
||||||
|
|
||||||
|
本课没有第三方依赖,因此不会提前创建包含虚假依赖的 `requirements.txt`。
|
||||||
|
|
||||||
|
## 十六、不要提交 `.venv`
|
||||||
|
|
||||||
|
`.venv` 可能包含大量文件,并带有当前机器的路径和平台信息,不适合提交到 Git。
|
||||||
|
|
||||||
|
应该提交的是:
|
||||||
|
|
||||||
|
- Python 源代码;
|
||||||
|
- 课程文档;
|
||||||
|
- 经过确认的依赖清单。
|
||||||
|
|
||||||
|
不应该提交的是:
|
||||||
|
|
||||||
|
- `.venv/`;
|
||||||
|
- `__pycache__/`;
|
||||||
|
- 本地密钥和 `.env`。
|
||||||
|
|
||||||
|
本项目根目录 `.gitignore` 已包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十七、完整示例
|
||||||
|
|
||||||
|
示例文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
02_python进阶/2_8_虚拟环境与依赖管理/environment_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
它只读取并输出:
|
||||||
|
|
||||||
|
- Python 版本;
|
||||||
|
- 当前解释器路径;
|
||||||
|
- 当前环境目录名称;
|
||||||
|
- 是否为普通 `venv` 虚拟环境;
|
||||||
|
- 当前环境的 pip 版本。
|
||||||
|
|
||||||
|
示例不会创建环境,也不会安装或卸载依赖。
|
||||||
|
|
||||||
|
## 十八、运行方法
|
||||||
|
|
||||||
|
未激活虚拟环境时运行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_8_虚拟环境与依赖管理\environment_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
创建环境后,不激活而直接运行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe .\02_python进阶\2_8_虚拟环境与依赖管理\environment_example.py
|
||||||
|
```
|
||||||
|
|
||||||
|
完成代码练习后运行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\02_python进阶\2_8_虚拟环境与依赖管理\practice.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十九、预期结果
|
||||||
|
|
||||||
|
具体路径和版本因机器而异,格式类似:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Python 版本:3.x.x
|
||||||
|
Python 解释器:某个实际路径
|
||||||
|
当前环境目录:某个环境名称
|
||||||
|
是否为 venv 虚拟环境:是或否
|
||||||
|
pip 版本:某个实际版本
|
||||||
|
```
|
||||||
|
|
||||||
|
在普通环境中通常显示“否”;使用 `.venv\Scripts\python.exe` 运行时应显示“是”。
|
||||||
|
|
||||||
|
## 二十、常见错误
|
||||||
|
|
||||||
|
### 20.1 创建环境后仍然使用原 Python
|
||||||
|
|
||||||
|
创建 `.venv` 不等于已经切换环境。需要激活,或者直接调用 `.venv\Scripts\python.exe`。
|
||||||
|
|
||||||
|
### 20.2 把依赖安装到错误环境
|
||||||
|
|
||||||
|
安装前先执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -c "import sys; print(sys.executable)"
|
||||||
|
```
|
||||||
|
|
||||||
|
确认路径后再使用 `python -m pip install`。
|
||||||
|
|
||||||
|
### 20.3 把 `.venv` 提交到 Git
|
||||||
|
|
||||||
|
虚拟环境是本地生成内容,应由 `.gitignore` 忽略。其他机器根据依赖清单重新创建。
|
||||||
|
|
||||||
|
### 20.4 把依赖清单当成虚拟环境
|
||||||
|
|
||||||
|
`requirements.txt` 只记录依赖名称和版本,不包含已安装文件。仍需先创建环境,再安装清单。
|
||||||
|
|
||||||
|
### 20.5 认为激活会永久改变系统
|
||||||
|
|
||||||
|
激活主要影响当前终端。执行 `deactivate` 或关闭终端即可退出。
|
||||||
|
|
||||||
|
### 20.6 随意修改 PowerShell 执行策略
|
||||||
|
|
||||||
|
激活脚本被阻止时,可以直接使用 `.venv\Scripts\python.exe`,本课不要求修改系统级策略。
|
||||||
|
|
||||||
|
## 二十一、课堂练习
|
||||||
|
|
||||||
|
练习分为两部分。
|
||||||
|
|
||||||
|
终端操作:
|
||||||
|
|
||||||
|
1. 在普通环境运行 `environment_example.py` 并记录结果;
|
||||||
|
2. 在项目根目录创建 `.venv`;
|
||||||
|
3. 激活环境,或直接使用 `.venv\Scripts\python.exe`;
|
||||||
|
4. 再次运行示例并比较解释器路径;
|
||||||
|
5. 执行 `python -m pip --version` 和 `python -m pip list`;
|
||||||
|
6. 退出环境并确认解释器恢复。
|
||||||
|
|
||||||
|
代码练习:打开 `practice.py`,完成环境判断、版本读取、环境报告和输出函数。
|
||||||
|
|
||||||
|
## 二十二、参考答案
|
||||||
|
|
||||||
|
参考答案暂不写入练习文件。完成后,我会验证函数行为和类型注解,并分别在当前解释器与临时虚拟环境中运行,确认环境判断能够反映真实差异。
|
||||||
|
|
||||||
|
## 二十三、本课小结
|
||||||
|
|
||||||
|
- 虚拟环境为项目隔离 Python 解释器入口和依赖;
|
||||||
|
- 使用 `python -m venv .venv` 创建环境;
|
||||||
|
- 激活后,当前终端优先使用虚拟环境;
|
||||||
|
- 不激活时也可以直接调用虚拟环境中的 Python;
|
||||||
|
- `python -m pip` 能减少把依赖装错环境的风险;
|
||||||
|
- `requirements.txt` 用于记录和恢复依赖;
|
||||||
|
- 版本固定有助于保持不同机器的环境一致;
|
||||||
|
- `.venv` 是本地生成目录,不应提交到 Git。
|
||||||
|
|
||||||
|
## 二十四、验收标准
|
||||||
|
|
||||||
|
- 能解释虚拟环境解决的问题;
|
||||||
|
- 能创建、进入和退出 `.venv`;
|
||||||
|
- 能确认当前 Python 解释器路径;
|
||||||
|
- 能使用 `python -m pip` 查看依赖;
|
||||||
|
- 能说明安装与卸载会修改当前环境;
|
||||||
|
- 能解释 `requirements.txt` 的用途;
|
||||||
|
- 能说明版本固定的基本作用;
|
||||||
|
- 能解释为什么 `.venv` 不应提交;
|
||||||
|
- 环境检查脚本在普通环境与 `.venv` 中显示不同判断;
|
||||||
|
- 没有把个人绝对路径或虚假依赖写入项目文件。
|
||||||
45
02_python进阶/2_8_虚拟环境与依赖管理/environment_example.py
Normal file
45
02_python进阶/2_8_虚拟环境与依赖管理/environment_example.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# 第 2-8 课完整示例:虚拟环境与依赖管理
|
||||||
|
#
|
||||||
|
# 本示例只读取当前 Python 解释器和环境信息,不安装、卸载任何依赖。
|
||||||
|
|
||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
from importlib.metadata import PackageNotFoundError, version
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def is_virtual_environment() -> bool:
|
||||||
|
"""判断当前程序是否运行在普通 venv 虚拟环境中。"""
|
||||||
|
# 普通环境中 sys.prefix 和 sys.base_prefix 通常相同。
|
||||||
|
# venv 激活并使用其 Python 后,这两个路径通常不同。
|
||||||
|
return sys.prefix != sys.base_prefix
|
||||||
|
|
||||||
|
|
||||||
|
def get_package_version(package_name: str) -> str | None:
|
||||||
|
"""返回已安装依赖的版本,未安装时返回 None。"""
|
||||||
|
try:
|
||||||
|
return version(package_name)
|
||||||
|
except PackageNotFoundError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_environment_name() -> str:
|
||||||
|
"""返回当前环境目录名称。"""
|
||||||
|
return Path(sys.prefix).name
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""输出解释器、虚拟环境和 pip 版本信息。"""
|
||||||
|
in_virtual_environment = is_virtual_environment()
|
||||||
|
environment_status = "是" if in_virtual_environment else "否"
|
||||||
|
pip_version = get_package_version("pip")
|
||||||
|
|
||||||
|
print(f"Python 版本:{platform.python_version()}")
|
||||||
|
print(f"Python 解释器:{sys.executable}")
|
||||||
|
print(f"当前环境目录:{get_environment_name()}")
|
||||||
|
print(f"是否为 venv 虚拟环境:{environment_status}")
|
||||||
|
print(f"pip 版本:{pip_version}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
78
02_python进阶/2_8_虚拟环境与依赖管理/practice.py
Normal file
78
02_python进阶/2_8_虚拟环境与依赖管理/practice.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# 第 2-8 课课堂练习:检查虚拟环境
|
||||||
|
#
|
||||||
|
# 本文件只读取环境信息,不负责创建环境或安装依赖。
|
||||||
|
# 创建、激活和退出虚拟环境的操作请按照讲义在 PowerShell 中完成。
|
||||||
|
# 不要删除题目、预期结果和自查注释。
|
||||||
|
|
||||||
|
# 第一部分:定义 is_virtual_environment() 函数
|
||||||
|
# 1. 不接收参数,返回值标注为 bool。
|
||||||
|
# 2. 比较 sys.prefix 和 sys.base_prefix。
|
||||||
|
# 3. 两者不相等时返回 True,否则返回 False。
|
||||||
|
# 4. 不要根据路径中是否包含“.venv”来判断。
|
||||||
|
|
||||||
|
# 第二部分:定义 get_python_version() 函数
|
||||||
|
# 1. 不接收参数,返回值标注为 str。
|
||||||
|
# 2. 调用 platform.python_version() 并 return 结果。
|
||||||
|
# 3. 不要手工写死版本号,因为不同机器的版本可能不同。
|
||||||
|
|
||||||
|
# 第三部分:定义 get_environment_name() 函数
|
||||||
|
# 1. 不接收参数,返回值标注为 str。
|
||||||
|
# 2. 使用 Path(sys.prefix).name 取得当前环境目录名称。
|
||||||
|
# 3. return 得到的名称。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 第四部分:定义 get_environment_report() 函数
|
||||||
|
# 1. 不接收参数,返回值标注为 dict[str, str]。
|
||||||
|
# 2. 调用前面三个函数。
|
||||||
|
# 3. 把布尔值转换为中文:“是”或“否”。
|
||||||
|
# 4. return 包含以下三个键的字典:
|
||||||
|
# "python_version":当前 Python 版本;
|
||||||
|
# "environment_name":当前环境目录名称;
|
||||||
|
# "is_virtual_environment":中文“是”或“否”。
|
||||||
|
|
||||||
|
|
||||||
|
# 第五部分:定义 print_environment_report(report) 函数
|
||||||
|
# 1. 参数标注为 dict[str, str],返回值标注为 None。
|
||||||
|
# 2. 依次输出:
|
||||||
|
# Python 版本:实际版本
|
||||||
|
# 当前环境:实际环境名称
|
||||||
|
# 是否为虚拟环境:是或否
|
||||||
|
# 3. 本函数只负责输出,不修改 report。
|
||||||
|
|
||||||
|
|
||||||
|
# 第六部分:定义 main() 函数
|
||||||
|
# 1. 返回值标注为 None。
|
||||||
|
# 2. 调用 get_environment_report() 并保存结果。
|
||||||
|
# 3. 调用 print_environment_report() 输出报告。
|
||||||
|
# 4. 添加程序入口判断,直接运行本文件时调用 main()。
|
||||||
|
|
||||||
|
|
||||||
|
# 第七部分:在两个环境中运行
|
||||||
|
# 1. 未激活本课虚拟环境时运行一次 practice.py,记录输出。
|
||||||
|
# 2. 按讲义创建并激活项目根目录下的 .venv。
|
||||||
|
# 3. 再次运行 practice.py。
|
||||||
|
# 4. 对比两次的环境名称和“是否为虚拟环境”。
|
||||||
|
# 5. 两台机器的路径和 Python 版本可能不同,不要求写死相同结果。
|
||||||
|
|
||||||
|
|
||||||
|
# 最终验收测试:
|
||||||
|
# 1. 所有函数的参数和返回值注解正确;
|
||||||
|
# 2. Python 版本来自 platform.python_version();
|
||||||
|
# 3. 环境名称来自 Path(sys.prefix).name;
|
||||||
|
# 4. 虚拟环境判断来自 sys.prefix != sys.base_prefix;
|
||||||
|
# 5. 报告字典包含三个指定键,值都是字符串;
|
||||||
|
# 6. 输出函数不修改报告字典;
|
||||||
|
# 7. 普通环境与 .venv 中的输出能够体现环境差异;
|
||||||
|
# 8. 没有在代码中写死个人机器路径或 Python 版本;
|
||||||
|
# 9. 没有在脚本中自动安装或卸载依赖。
|
||||||
|
|
||||||
|
|
||||||
|
# 完成后自查:
|
||||||
|
# 1. 是否知道虚拟环境用于隔离项目依赖;
|
||||||
|
# 2. 是否知道激活环境不会删除系统 Python;
|
||||||
|
# 3. 是否优先使用 python -m pip;
|
||||||
|
# 4. 是否知道 requirements.txt 用于记录项目依赖;
|
||||||
|
# 5. 是否知道 .venv 不应提交到 Git;
|
||||||
|
# 6. 是否能退出环境并重新激活;
|
||||||
|
# 7. 是否保留了完整题目和验收说明。
|
||||||
405
02_python进阶/2_8_虚拟环境与依赖管理/补充_conda_pip_uv与虚拟环境的区别.md
Normal file
405
02_python进阶/2_8_虚拟环境与依赖管理/补充_conda_pip_uv与虚拟环境的区别.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# 补充:Conda、pip、uv 与 Python 虚拟环境的区别
|
||||||
|
|
||||||
|
## 一、先给出结论
|
||||||
|
|
||||||
|
这些名称不完全属于同一类工具:
|
||||||
|
|
||||||
|
- `venv`、`virtualenv`:主要负责创建相互隔离的 Python 环境;
|
||||||
|
- `pip`:主要负责向某个 Python 环境安装和卸载 Python 软件包,本身不负责创建虚拟环境;
|
||||||
|
- Conda:同时管理环境、Python 版本、Python 包和部分非 Python 软件;
|
||||||
|
- `uv`:同时覆盖 Python 版本、虚拟环境、依赖安装、项目依赖和锁文件等工作。
|
||||||
|
|
||||||
|
因此,“Conda、pip、uv 哪个虚拟环境更好”这个问题并不完全准确。更合适的问题是:
|
||||||
|
|
||||||
|
> 当前项目应该使用哪一种环境管理与依赖管理组合?
|
||||||
|
|
||||||
|
## 二、几个概念不要混在一起
|
||||||
|
|
||||||
|
### 2.1 Python 解释器
|
||||||
|
|
||||||
|
Python 解释器是实际运行 `.py` 文件的程序,例如当前机器检测到的:
|
||||||
|
|
||||||
|
```text
|
||||||
|
C:\ProgramData\miniconda3\python.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
不同环境可能有自己的解释器入口,也可能使用不同的 Python 版本。
|
||||||
|
|
||||||
|
### 2.2 虚拟环境
|
||||||
|
|
||||||
|
虚拟环境负责隔离项目使用的 Python 和已安装软件包。例如项目 A 与项目 B 可以分别拥有自己的 `.venv`,互不影响。
|
||||||
|
|
||||||
|
### 2.3 包管理器
|
||||||
|
|
||||||
|
包管理器负责查找、安装、更新和卸载软件包:
|
||||||
|
|
||||||
|
- `pip` 主要管理 Python 软件包;
|
||||||
|
- Conda 可以管理 Conda 软件包,其中可以包含 Python、Python 包以及其他软件;
|
||||||
|
- `uv` 可以通过项目工作流或兼容 pip 的命令管理 Python 依赖。
|
||||||
|
|
||||||
|
### 2.4 依赖解析与锁定
|
||||||
|
|
||||||
|
依赖解析负责计算“安装哪些版本才能彼此兼容”。锁文件用于记录解析后的精确结果,使其他机器尽量复现相同环境。
|
||||||
|
|
||||||
|
常见文件包括:
|
||||||
|
|
||||||
|
- pip 工作流:`requirements.txt`;
|
||||||
|
- Conda 工作流:`environment.yml`,也可配合相应锁文件;
|
||||||
|
- uv 项目工作流:`pyproject.toml` 和 `uv.lock`。
|
||||||
|
|
||||||
|
## 三、`venv`:Python 自带的基础选择
|
||||||
|
|
||||||
|
`venv` 是 Python 标准库中的虚拟环境工具,不需要单独安装。
|
||||||
|
|
||||||
|
创建环境:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m venv .venv
|
||||||
|
```
|
||||||
|
|
||||||
|
激活:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
在环境中安装包:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip install 软件包名称
|
||||||
|
```
|
||||||
|
|
||||||
|
特点:
|
||||||
|
|
||||||
|
- Python 自带,概念简单;
|
||||||
|
- 通常基于创建它的现有 Python;
|
||||||
|
- 只负责创建环境,不负责完整项目锁定;
|
||||||
|
- 通常与 `pip`、`requirements.txt` 一起使用;
|
||||||
|
- 适合普通 Python 脚本、Web 后端和入门学习。
|
||||||
|
|
||||||
|
典型组合:
|
||||||
|
|
||||||
|
```text
|
||||||
|
venv 创建环境
|
||||||
|
+ pip 安装依赖
|
||||||
|
+ requirements.txt 记录依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、`virtualenv`:功能更丰富的环境创建工具
|
||||||
|
|
||||||
|
`virtualenv` 是需要额外安装的第三方工具。Python 官方打包指南说明,`venv` 是 Python 自带方案,而 `virtualenv` 提供更广的兼容性和额外能力。
|
||||||
|
|
||||||
|
基本命令:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
virtualenv .venv
|
||||||
|
```
|
||||||
|
|
||||||
|
特点:
|
||||||
|
|
||||||
|
- 主要职责与 `venv` 相同,都是创建隔离环境;
|
||||||
|
- 需要先安装;
|
||||||
|
- 提供更丰富的解释器选择、配置和兼容能力;
|
||||||
|
- 在只需要基础环境隔离的新项目中,通常先用 `venv` 就够了。
|
||||||
|
|
||||||
|
对当前零基础课程,不必同时学习 `venv` 和 `virtualenv`。先理解 `venv`,以后遇到明确需求再使用 `virtualenv`。
|
||||||
|
|
||||||
|
## 五、`pip`:软件包安装器,不是虚拟环境
|
||||||
|
|
||||||
|
`pip` 的职责是把 Python 软件包安装到某个环境。
|
||||||
|
|
||||||
|
推荐命令:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip install 软件包名称
|
||||||
|
```
|
||||||
|
|
||||||
|
这里的关键是“当前这个 Python”:
|
||||||
|
|
||||||
|
- 如果当前 Python 来自 `.venv`,依赖会装入 `.venv`;
|
||||||
|
- 如果当前 Python 来自 Conda 环境,依赖会装入该 Conda 环境;
|
||||||
|
- 如果当前 Python 是全局环境,依赖可能被装入全局环境。
|
||||||
|
|
||||||
|
所以 `pip` 自己不提供隔离,隔离由 `venv`、`virtualenv` 或 Conda 环境等机制提供。
|
||||||
|
|
||||||
|
`pip` 常见能力:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m pip install 软件包名称
|
||||||
|
python -m pip uninstall 软件包名称
|
||||||
|
python -m pip list
|
||||||
|
python -m pip show 软件包名称
|
||||||
|
python -m pip check
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- Python 生态中最通用;
|
||||||
|
- PyPI 上的软件包覆盖广;
|
||||||
|
- 与 `venv` 配合简单;
|
||||||
|
- 教程、部署平台和持续集成环境普遍支持。
|
||||||
|
|
||||||
|
需要注意:
|
||||||
|
|
||||||
|
- 安装前必须确认当前解释器;
|
||||||
|
- `requirements.txt` 与环境本身是两回事;
|
||||||
|
- `pip freeze` 会导出当前环境全部软件包,环境不干净时可能带入无关依赖。
|
||||||
|
|
||||||
|
## 六、Conda:环境管理器加跨语言包管理器
|
||||||
|
|
||||||
|
Conda 同时负责环境和包管理。它可以在创建环境时指定 Python 版本:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda create -n python-course python=3.13
|
||||||
|
conda activate python-course
|
||||||
|
```
|
||||||
|
|
||||||
|
安装 Conda 软件包:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda install 软件包名称
|
||||||
|
```
|
||||||
|
|
||||||
|
查看环境:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda env list
|
||||||
|
```
|
||||||
|
|
||||||
|
Conda 环境与普通 `venv` 的重要区别:
|
||||||
|
|
||||||
|
- Conda 可以直接管理环境中的 Python 版本;
|
||||||
|
- Conda 包不局限于纯 Python 包,也能包含本地库、命令行程序等内容;
|
||||||
|
- 软件包来自 Conda channel,例如 `defaults` 或 `conda-forge`;
|
||||||
|
- 环境通常集中保存在 Conda 安装目录的 `envs` 下,也可以使用指定路径;
|
||||||
|
- 环境描述常使用 `environment.yml`。
|
||||||
|
|
||||||
|
Conda 更适合:
|
||||||
|
|
||||||
|
- 数据科学、机器学习和科学计算;
|
||||||
|
- 依赖 NumPy、PyTorch、CUDA 或本地二进制库的项目;
|
||||||
|
- 需要同时管理 Python 与非 Python 软件的环境;
|
||||||
|
- 已经以 Anaconda、Miniconda 或 Miniforge 作为主要 Python 发行方式的团队。
|
||||||
|
|
||||||
|
代价和注意事项:
|
||||||
|
|
||||||
|
- 工具体系比 `venv + pip` 更大;
|
||||||
|
- channel 和包来源会影响解析结果;
|
||||||
|
- Conda 包版本可能与 PyPI 发布节奏不同;
|
||||||
|
- 同一环境混用 Conda 与 pip 时需要保持清晰边界。
|
||||||
|
|
||||||
|
## 七、Conda 环境中能不能使用 pip
|
||||||
|
|
||||||
|
可以,但建议遵守以下顺序:
|
||||||
|
|
||||||
|
1. 创建并激活独立 Conda 环境;
|
||||||
|
2. 优先一次性安装需要的 Conda 包;
|
||||||
|
3. 确认使用的是当前环境中的 pip;
|
||||||
|
4. 只有 Conda channel 没有需要的软件包时,再使用 pip;
|
||||||
|
5. pip 安装后如果还要大规模调整 Conda 依赖,优先考虑重建环境,而不是反复混装。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda create -n python-course python=3.13 pip
|
||||||
|
conda activate python-course
|
||||||
|
python -c "import sys; print(sys.executable)"
|
||||||
|
python -m pip --version
|
||||||
|
```
|
||||||
|
|
||||||
|
然后才能确认 pip 指向当前 Conda 环境。
|
||||||
|
|
||||||
|
不建议在一个环境中无计划地交替执行大量 `conda install` 和 `pip install`。两套工具使用不同的软件包格式与依赖信息,复杂项目可能出现一方不知道另一方具体修改了什么的问题。
|
||||||
|
|
||||||
|
## 八、`uv`:速度较快的一体化 Python 工具
|
||||||
|
|
||||||
|
`uv` 是独立工具,不依赖当前 Python 才能启动。它覆盖多项工作:
|
||||||
|
|
||||||
|
- 安装和选择 Python;
|
||||||
|
- 创建虚拟环境;
|
||||||
|
- 安装与卸载 Python 软件包;
|
||||||
|
- 管理 `pyproject.toml` 项目;
|
||||||
|
- 生成并使用 `uv.lock`;
|
||||||
|
- 同步项目环境;
|
||||||
|
- 在项目环境中运行命令。
|
||||||
|
|
||||||
|
仅创建虚拟环境:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv venv
|
||||||
|
```
|
||||||
|
|
||||||
|
使用类似 pip 的接口:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv pip install 软件包名称
|
||||||
|
uv pip list
|
||||||
|
uv pip check
|
||||||
|
```
|
||||||
|
|
||||||
|
完整项目工作流:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv init
|
||||||
|
uv add 软件包名称
|
||||||
|
uv sync
|
||||||
|
uv run python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
在项目工作流中:
|
||||||
|
|
||||||
|
- `pyproject.toml` 声明项目及直接依赖;
|
||||||
|
- `uv.lock` 记录解析后的精确依赖;
|
||||||
|
- 默认项目环境通常是 `.venv`;
|
||||||
|
- `uv run` 会在运行前确认锁文件和项目环境保持同步。
|
||||||
|
|
||||||
|
官方文档建议:使用 uv 项目工作流时,通过 `uv add` 管理项目依赖,不要把 `uv pip install` 当作项目依赖声明方式。`uv pip` 更适合兼容传统 pip 工作流或手动管理环境。
|
||||||
|
|
||||||
|
## 九、`uv` 与 pip 的关系
|
||||||
|
|
||||||
|
`uv pip` 提供与 pip 类似的命令体验,但 `uv` 并不是 pip 的插件,也不是简单调用 pip。
|
||||||
|
|
||||||
|
可以这样理解:
|
||||||
|
|
||||||
|
```text
|
||||||
|
传统方式:venv + pip + requirements.txt
|
||||||
|
uv 兼容方式:uv venv + uv pip + requirements.txt
|
||||||
|
uv 项目方式:uv + pyproject.toml + uv.lock + .venv
|
||||||
|
```
|
||||||
|
|
||||||
|
如果使用完整 uv 项目工作流,常用命令通常是:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv add
|
||||||
|
uv remove
|
||||||
|
uv sync
|
||||||
|
uv run
|
||||||
|
```
|
||||||
|
|
||||||
|
而不是手动激活环境后不断执行 `uv pip install`。
|
||||||
|
|
||||||
|
## 十、`uv` 与 Conda 的区别
|
||||||
|
|
||||||
|
两者都能管理 Python 和环境,但侧重点不同。
|
||||||
|
|
||||||
|
Conda:
|
||||||
|
|
||||||
|
- 以 Conda 包、channel 和 Conda 环境为核心;
|
||||||
|
- 能管理更广泛的非 Python 依赖;
|
||||||
|
- 在科学计算、数据分析和机器学习生态中常见;
|
||||||
|
- 可以管理 Python、R、系统库和二进制程序等内容。
|
||||||
|
|
||||||
|
uv:
|
||||||
|
|
||||||
|
- 以现代 Python 项目、PyPI、`pyproject.toml` 和锁文件为核心;
|
||||||
|
- 默认使用项目内 `.venv`;
|
||||||
|
- 适合普通 Python 应用、库、工具和 Web 后端;
|
||||||
|
- 依赖操作和环境同步速度通常是其主要优势之一。
|
||||||
|
|
||||||
|
如果项目强依赖 CUDA、本地科学计算库或 Conda channel,Conda 通常更自然。如果项目主要是 PyPI 中的 Python 依赖,uv 项目工作流通常更轻量。
|
||||||
|
|
||||||
|
## 十一、核心对比表
|
||||||
|
|
||||||
|
| 工具 | 创建隔离环境 | 安装 Python 包 | 管理 Python 版本 | 管理非 Python 软件 | 典型依赖文件 | 适合场景 |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| `venv` | 是 | 否,通常配合 pip | 否,使用已有 Python | 否 | 无,常配合 `requirements.txt` | 入门、普通脚本、Web 项目 |
|
||||||
|
| `virtualenv` | 是 | 否,通常配合 pip | 选择已有解释器 | 否 | 无,常配合 `requirements.txt` | 需要更强环境创建能力 |
|
||||||
|
| `pip` | 否 | 是 | 否 | 通常不负责 | `requirements.txt` | 向现有 Python 环境安装包 |
|
||||||
|
| Conda | 是 | 是 | 是 | 是 | `environment.yml` 等 | 数据科学、机器学习、二进制依赖 |
|
||||||
|
| `uv` | 是 | 是 | 是 | 主要面向 Python 生态 | `pyproject.toml`、`uv.lock` | 现代 Python 项目和 Web 后端 |
|
||||||
|
|
||||||
|
## 十二、应该如何选择
|
||||||
|
|
||||||
|
### 12.1 零基础学习和理解原理
|
||||||
|
|
||||||
|
优先学习:
|
||||||
|
|
||||||
|
```text
|
||||||
|
venv + pip
|
||||||
|
```
|
||||||
|
|
||||||
|
原因是它们能清楚展示“环境隔离”和“包安装”是两个不同职责,也是 Python 官方文档中的基础工作流。
|
||||||
|
|
||||||
|
### 12.2 普通 Python、FastAPI 或 Django 项目
|
||||||
|
|
||||||
|
可以选择:
|
||||||
|
|
||||||
|
```text
|
||||||
|
venv + pip
|
||||||
|
```
|
||||||
|
|
||||||
|
或者:
|
||||||
|
|
||||||
|
```text
|
||||||
|
uv 项目工作流
|
||||||
|
```
|
||||||
|
|
||||||
|
前者通用、容易理解;后者在依赖解析、锁定和同步方面更加一体化。
|
||||||
|
|
||||||
|
### 12.3 数据科学和机器学习
|
||||||
|
|
||||||
|
如果涉及复杂科学计算、CUDA 或系统二进制依赖,可以优先考虑:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Conda
|
||||||
|
```
|
||||||
|
|
||||||
|
必要时在独立 Conda 环境中谨慎补充 pip 包。
|
||||||
|
|
||||||
|
### 12.4 当前教学项目
|
||||||
|
|
||||||
|
当前机器的 `python` 来自 Miniconda,但这不等于每个项目都必须使用 Conda 环境。
|
||||||
|
|
||||||
|
为了理解基础概念,本课程讲义使用 `venv + pip`。以后进入 FastAPI 和 Django 阶段,可以根据学习目标二选一:
|
||||||
|
|
||||||
|
- 继续使用 `venv + pip`,保持通用和透明;
|
||||||
|
- 切换到 uv 项目工作流,学习现代依赖锁定与同步。
|
||||||
|
|
||||||
|
不建议在同一个项目中同时维护 `requirements.txt`、`environment.yml` 和 `uv.lock` 三套互不一致的依赖事实来源。应该选定一种主要工作流。
|
||||||
|
|
||||||
|
## 十三、常见误区
|
||||||
|
|
||||||
|
### 13.1 “pip 创建了虚拟环境”
|
||||||
|
|
||||||
|
不正确。通常是 `venv` 创建环境,pip 向环境安装软件包。
|
||||||
|
|
||||||
|
### 13.2 “激活环境后就安装了依赖”
|
||||||
|
|
||||||
|
不正确。激活只切换当前终端优先使用的解释器和工具,依赖仍需单独安装或同步。
|
||||||
|
|
||||||
|
### 13.3 “Conda 环境就是 Python venv”
|
||||||
|
|
||||||
|
两者都能实现隔离,但内部结构、包格式、依赖来源和管理范围不同,不能简单视为同一个实现。
|
||||||
|
|
||||||
|
### 13.4 “uv pip 就是速度更快的 pip 命令别名”
|
||||||
|
|
||||||
|
不准确。它提供兼容 pip 的操作方式,但 uv 是独立实现,并且还有完整的项目、Python、环境和锁文件管理能力。
|
||||||
|
|
||||||
|
### 13.5 “有锁文件就不需要虚拟环境”
|
||||||
|
|
||||||
|
不正确。锁文件描述应该安装什么,虚拟环境保存实际安装结果,两者职责不同。
|
||||||
|
|
||||||
|
### 13.6 “把 `.venv` 复制到另一台机器就能复用”
|
||||||
|
|
||||||
|
不推荐。虚拟环境可能包含绝对路径和平台相关文件。应在新机器重新创建环境,并根据依赖文件安装或同步。
|
||||||
|
|
||||||
|
## 十四、推荐记忆方式
|
||||||
|
|
||||||
|
```text
|
||||||
|
venv / virtualenv:创建隔离房间
|
||||||
|
pip:向房间里安装 Python 包
|
||||||
|
Conda:创建房间,并管理 Python 与更广的软件
|
||||||
|
uv:创建房间,并管理现代 Python 项目、依赖和锁文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十五、官方资料
|
||||||
|
|
||||||
|
- [Python 打包指南:使用 pip 和 venv](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)
|
||||||
|
- [Python 打包指南:安装软件包](https://packaging.python.org/en/latest/tutorials/installing-packages/)
|
||||||
|
- [virtualenv 官方文档](https://virtualenv.pypa.io/en/stable/)
|
||||||
|
- [Conda:管理环境](https://docs.conda.io/projects/conda/en/stable/user-guide/tasks/manage-environments.html)
|
||||||
|
- [Conda:管理软件包](https://docs.conda.io/projects/conda/en/stable/user-guide/tasks/manage-pkgs.html)
|
||||||
|
- [uv:Python 环境](https://docs.astral.sh/uv/pip/environments/)
|
||||||
|
- [uv:项目结构、环境与锁文件](https://docs.astral.sh/uv/concepts/projects/layout/)
|
||||||
|
- [uv:从 pip 工作流迁移到 uv 项目](https://docs.astral.sh/uv/guides/migration/pip-to-project/)
|
||||||
34
README.md
34
README.md
@@ -176,6 +176,14 @@ Python/
|
|||||||
│ ├── 1_2_变量与数据类型/
|
│ ├── 1_2_变量与数据类型/
|
||||||
│ └── 1_3_输入与输出/
|
│ └── 1_3_输入与输出/
|
||||||
├── 02_python进阶/
|
├── 02_python进阶/
|
||||||
|
│ ├── 2_1_模块与包/
|
||||||
|
│ ├── 2_2_文件与目录操作/
|
||||||
|
│ ├── 2_3_异常处理/
|
||||||
|
│ ├── 2_4_推导式与简化写法/
|
||||||
|
│ ├── 2_5_迭代器与生成器/
|
||||||
|
│ ├── 2_6_装饰器/
|
||||||
|
│ ├── 2_7_类型注解/
|
||||||
|
│ └── 2_8_虚拟环境与依赖管理/
|
||||||
├── 03_面向对象/
|
├── 03_面向对象/
|
||||||
├── 04_数据库/
|
├── 04_数据库/
|
||||||
├── 05_web基础/
|
├── 05_web基础/
|
||||||
@@ -189,12 +197,12 @@ Python/
|
|||||||
## 当前学习进度
|
## 当前学习进度
|
||||||
|
|
||||||
- 当前阶段:第二阶段——Python 进阶。
|
- 当前阶段:第二阶段——Python 进阶。
|
||||||
- 当前课程:`2_2_文件与目录操作`。
|
- 当前课程:第二阶段综合项目准备。
|
||||||
- 当前状态:正在学习文件与目录操作。
|
- 当前状态:`2_8_虚拟环境与依赖管理` 已按学习者要求跳过并标记为完成,同时已补充 Conda、pip、uv 对比文档。
|
||||||
- 已完成课程:`1_1_hello_world` 至 `1_14_python基础综合项目`,以及 `2_1_模块与包`。
|
- 已完成课程:`1_1_hello_world` 至 `1_14_python基础综合项目`,以及第二阶段 `2_1_模块与包` 至 `2_8_虚拟环境与依赖管理`。
|
||||||
- 学习中的课程:`2_2_文件与目录操作`。
|
- 学习中的课程:暂无。
|
||||||
- 已创建课程目录:第一阶段全部课程,以及 `02_python进阶/2_1_模块与包/`、`02_python进阶/2_2_文件与目录操作/`。
|
- 已创建课程目录:第一阶段全部课程,以及第二阶段 `2_1_模块与包` 至 `2_8_虚拟环境与依赖管理`。
|
||||||
- 下一步:完成文件与目录操作练习,然后学习异常处理。
|
- 下一步:进入 `2_9_python进阶综合项目`。
|
||||||
|
|
||||||
## 建议环境
|
## 建议环境
|
||||||
|
|
||||||
@@ -209,12 +217,12 @@ Python/
|
|||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
上一课“模块与包”已经完成。当前正在学习文件与目录操作,本课将学习:
|
第二阶段第 8 课已按学习者要求跳过并标记为完成,同时保留补充资料供以后查阅。下一步将进入综合项目,综合使用:
|
||||||
|
|
||||||
1. 文件、目录和路径的基本含义;
|
1. 模块与包;
|
||||||
2. 使用 `Path` 表示和组合路径;
|
2. 文件读写与异常处理;
|
||||||
3. 创建练习目录;
|
3. 推导式、生成器和类型注解;
|
||||||
4. 写入、追加和读取文本文件;
|
4. 使用多个文件组织任务管理程序;
|
||||||
5. 检查路径并遍历目录内容。
|
5. 保存并恢复任务数据。
|
||||||
|
|
||||||
完成本课练习并确认继续后,下一课将学习异常处理。
|
确认继续后,将创建 `2_9_python进阶综合项目`。
|
||||||
|
|||||||
Reference in New Issue
Block a user