《Python AI 应用开发入门》第 2.7 节。
使用 for 和 while 重复处理数据,并完成命令行聊天记录管理程序。
本节目标
学完本节后,你应当能够:
- 1. 使用
for 遍历字符串、列表、字典和集合。 - 2. 使用
range() 和 enumerate() 生成序号。 - 4. 使用
break 和 continue 控制循环。 - 5. 识别死循环、越界和循环中修改容器等常见问题。
1. 为什么需要循环
没有循环时,显示三条消息可能写成:
messages = ["你好", "请解释循环", "谢谢"]
print(messages[0])
print(messages[1])
print(messages[2])
这段代码只适用于恰好三条消息。循环可以对每个元素执行同一操作:
for message in messages:
print(message)
消息数量变化时,代码仍然有效。
2. for 循环
基本结构:
for 临时变量 in 可迭代对象:
重复执行的代码
遍历字符串:
for character in "AI":
print(character)
遍历列表:
messages = ["你好", "请解释循环", "谢谢"]
for message in messages:
print(f"消息:{message}")
每轮循环中,message 依次指向列表中的一个元素。
循环结束后才执行取消缩进的代码:
for message in messages:
print(message)
print("全部消息显示完毕")
3. range() 生成整数序列
for number in range(5):
print(number)
输出 0 到 4,不包含结束值 5。
常见形式:
range(5) # 0, 1, 2, 3, 4
range(1, 5) # 1, 2, 3, 4
range(1, 10, 2) # 1, 3, 5, 7, 9
重复固定次数:
for attempt in range(1, 4):
print(f"第 {attempt} 次尝试")
不需要循环变量时,通常使用下划线:
for _ in range(3):
print("正在重试")
4. enumerate() 同时获得序号和值
显示聊天历史时通常需要编号:
messages = ["你好", "什么是循环?", "谢谢"]
for index, message in enumerate(messages, start=1):
print(f"{index}. {message}")
start=1 只改变显示序号,不会改变列表真实索引。列表第一项仍然是 messages[0]。
不要为了取得索引而优先写复杂的 range(len(...)):
for index in range(len(messages)):
print(index + 1, messages[index])
它可以工作,但只需要“序号和值”时,enumerate() 更清楚。
5. 遍历字典
message = {
"role": "user",
"content": "请解释循环",
}
直接遍历得到键:
for key in message:
print(key)
同时取得键和值:
for key, value in message.items():
print(f"{key}: {value}")
遍历聊天消息列表:
messages = [
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!"},
]
for index, message in enumerate(messages, start=1):
role = message["role"]
content = message["content"]
print(f"{index}. [{role}] {content}")
6. 遍历集合
keywords = {"Python", "AI", "循环"}
for keyword in keywords:
print(keyword)
不能依赖集合的遍历顺序。需要稳定显示时先排序:
for keyword in sorted(keywords):
print(keyword)
7. while 循环
while 在条件为真时持续执行:
count = 1
while count <= 3:
print(f"第 {count} 轮")
count += 1
执行过程:
如果忘记更新 count,循环可能永远无法结束。
8. break 主动结束循环
菜单程序不知道用户要输入多少次,因此常使用无限循环,再通过 break 退出:
while True:
command = input("输入命令,exit 退出:").strip().lower()
if command == "exit":
print("程序结束")
break
print(f"收到命令:{command}")
break 只结束它所在的最内层循环。
9. continue 跳过当前轮
while True:
question = input("请输入问题,exit 退出:").strip()
if question.lower() == "exit":
break
if not question:
print("问题不能为空")
continue
print(f"已保存:{question}")
空输入时执行 continue,立即回到循环开头,不执行后面的保存逻辑。
continue 适合先排除无效情况,让主要逻辑减少嵌套。
10. 循环中的统计
统计角色数量:
messages = [
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!"},
{"role": "user", "content": "什么是循环?"},
]
role_counts = {}
for message in messages:
role = message["role"]
role_counts[role] = role_counts.get(role, 0) + 1
print(role_counts)
每轮读取旧计数,增加 1,再写回字典。
按关键词搜索:
keyword = "循环"
match_count = 0
for message in messages:
content = message["content"]
if keyword.lower() in content.lower():
print(content)
match_count += 1
print(f"找到 {match_count} 条")
11. 嵌套循环
循环内部可以再循环:
conversations = [
["你好", "你好!"],
["什么是列表?", "列表是可变序列。"],
]
for conversation_index, conversation in enumerate(conversations, start=1):
print(f"会话 {conversation_index}")
for message in conversation:
print(f"- {message}")
内层循环会对外层每一项完整执行一次。嵌套过多会让代码难以阅读,先确认数据是否真的需要多层结构。
12. for 与 while 如何选择
简单判断:
- • 只知道继续条件,不知道次数,使用
while。
13. 常见循环问题
死循环
count = 1
while count <= 3:
print(count)
count 没有变化,条件一直为真。修复为:
count += 1
range() 结束值不包含
range(1, 4) 是 1、2、3,不是 1、2、3、4。
在遍历时修改同一个列表
numbers = [1, 2, 3, 4]
for number in numbers:
if number % 2 == 0:
numbers.remove(number)
这种写法可能跳过元素。当前章节可以创建新列表:
odd_numbers = []
for number in numbers:
if number % 2 != 0:
odd_numbers.append(number)
变量名混淆
for message in messages:
print(messages)
这会每轮打印整个列表。需要当前项时应该打印 message。
循环外误用结果
如果列表为空,循环体一次也不会执行。不要假设循环中的变量一定被赋值:
messages = []
for message in messages:
last_message = message
# print(last_message) # 变量可能不存在
14. 章节项目:命令行聊天记录
创建 chat_history_cli.py:
messages = []
allowed_roles = ("user", "assistant", "system")
print("聊天记录管理器")
while True:
print("\n1. 添加消息")
print("2. 查看历史")
print("3. 搜索消息")
print("4. 查看统计")
print("5. 清空历史")
print("0. 退出")
command = input("请选择功能:").strip()
if command == "1":
role = input("角色 user/assistant/system:").strip().lower()
if role not in allowed_roles:
print("角色无效")
continue
content = input("消息内容:").strip()
if not content:
print("消息不能为空")
continue
messages.append({
"role": role,
"content": content,
})
print("消息已保存")
elif command == "2":
if not messages:
print("暂无历史消息")
continue
for index, message in enumerate(messages, start=1):
print(f"{index}. [{message['role']}] {message['content']}")
elif command == "3":
keyword = input("搜索关键词:").strip()
if not keyword:
print("关键词不能为空")
continue
match_count = 0
for index, message in enumerate(messages, start=1):
if keyword.lower() in message["content"].lower():
print(f"{index}. [{message['role']}] {message['content']}")
match_count += 1
print(f"共找到 {match_count} 条消息")
elif command == "4":
role_counts = {}
for message in messages:
role = message["role"]
role_counts[role] = role_counts.get(role, 0) + 1
print(f"消息总数:{len(messages)}")
for role in allowed_roles:
print(f"{role}: {role_counts.get(role, 0)}")
elif command == "5":
confirmation = input("确认清空?输入 yes:").strip().lower()
if confirmation == "yes":
messages.clear()
print("历史已清空")
else:
print("已取消")
elif command == "0":
print("再见!")
break
else:
print("无效选项,请重新输入")
运行:
python chat_history_cli.py
至少测试:
15. 当前版本的边界
程序退出后,messages 中的数据会消失,因为它只保存在内存中。这不是 Bug,而是当前版本的范围。
后续章节会逐步加入:
动手练习
练习 1:消息编号
使用 enumerate() 输出:
1. [user] 你好
2. [assistant] 你好!
练习 2:去重且保留顺序
给定:
tags = ["Python", "AI", "Python", "入门", "AI"]
使用循环创建 unique_tags 列表,要求结果保留首次出现顺序:
["Python", "AI", "入门"]
练习 3:完成章节项目
手写并测试 chat_history_cli.py。不要只复制示例;至少增加一个功能,例如:
随堂小测
- 3.
enumerate(messages, start=1) 的 start 会改变列表索引吗? - 4.
break 与 continue 有什么区别? - 7. 搜索消息时为什么常把关键词和内容都转为小写?
参考答案
- 1.
for 适合遍历已有数据或固定次数;while 适合次数未知、由条件决定是否继续的任务。 - 4.
break 结束整个当前循环;continue 跳过当前轮剩余代码,进入下一轮。 - 5. 列表长度和位置在遍历过程中改变,可能导致元素被跳过或结果难以预测。
- 6. 检查循环条件是否可能变为假,或是否存在能执行到的
break。
本节完成检查
- • 我会使用
range() 和 enumerate()。