第2章 2.2 循环:for/while/for...in

上一章我们学会了用 if 做判断,用三元运算符做二选一。但你有没有遇到过这种情况:老师让你把 1 到 100 的偶数打印出来,你第一反应是写 50 个 print()?或者要从 1000 条数据里找出符合条件的条目,总不能写 1000 个 if 吧?这一章我们要解决的问题就是:如何让计算机自动做重复的事。

你有没有发现,生活中大部分事情都是重复的?每天早上起床、洗漱、吃早饭、出门——这就是一个循环。程序也是一样,大部分时间我们都在让计算机重复执行某些操作。循环就是编程里的「复制粘贴」按钮,一旦写好,计算机就会帮你执行 N 遍。

学完这一章,你能:
- 用 for 遍历任何数据列表
- 用 while 处理不知道要循环多少次的情况
- 用 breakcontinue 控制循环的走向
- 写出一个能实际使用的小工具(比如处理 Excel 数据、筛选文件)


🧱 基础 25 分钟:核心概念

什么是循环?为什么要用循环?

类比\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\n\n:想象你在一个自助餐厅,菜单上有 10 道菜。你不会一道道跟服务员说「我要第 1 道」「我要第 2 道」——你只需要说「这 10 道,我每道都要尝一口」。循环就是这个意思:告诉计算机「这一堆东西,我每一个都要处理」

痛点:没有循环时,处理 100 条数据要写 100 行代码;有循环后,3 行搞定。

for 循环:已知次数的循环

最常用的循环方式。当你知道要循环多少次,或者有一个明确的列表要遍历时,用 for

# 打印 1 到 5
for i in range(1, 6):
print(f"第 {i} 次打印")
第 1 次打印
第 2 次打印
第 3 次打印
第 4 次打印
第 5 次打印

解释:range(1, 6) 生成 1, 2, 3, 4, 5 这 5 个数字,for i in 就是「把每个数字代进 i 里跑一遍」。

注意! range(1, 6) 实际上是 1 到 5,不包含 6。这是因为 Python 的惯例是「左闭右开」——左边包含,右边不包含。记不住?那就记住:range 几个数,就写比最后一个大 1 的数

遍历列表:for 的真正威力

for 最强大的地方是可以遍历任何「一堆东西」——列表、字符串、字典都行。

fruits = ["苹果", "香蕉", "橙子", "葡萄"]

for fruit in fruits:
print(f"我喜欢吃 {fruit}")
我喜欢吃 苹果
我喜欢吃 香蕉
我喜欢吃 橙子
我喜欢吃 葡萄

解释:fruit 这个变量名是你自己起的,每一轮循环,它会自动变成列表里的下一个元素。

while 循环:不知道要跑多少次

有时候你不知道要循环多少次,只知道「满足条件就一直跑」。比如:掷骰子,直到掷到 6 为止。

import random

result = 0
count = 0

while result != 6:
result = random.randint(1, 6)
count += 1
print(f"第 {count} 次,掷到 {result}")

print(f"终于掷到 6 了!总共掷了 {count} 次")
第 1 次,掷到 3
第 2 次,掷到 5
第 3 次,掷到 2
第 4 次,掷到 6
终于掷到 6 了!总共掷了 4 次

解释:while result != 6 的意思是「只要 result 不等于 6,就一直循环」。每次循环都会重新掷骰子,直到命中 6。

坑来了! while 循环一定要有结束条件,否则会变成「死循环」,程序卡死在那不动。如果你不确定会不会死循环,先想清楚「什么情况下会退出」。

for...in:Python 特有的遍历方式

Python 没有 for...of(那是 JavaScript 的语法),但 Python 的 for...in 更直观:

# 遍历字符串(每个字符)
message = "Hello"
for char in message:
print(char)
H
e
l
l
o
# 遍历字典(默认只遍历 key)
person = {"name": "小明", "age": 18, "city": "北京"}

for key in person:
print(f"{key} = {person[key]}")
name = 小明
age = 18
city = 北京
# 遍历字典的值
for value in person.values():
print(value)
小明
18
北京
# 同时遍历 key 和 value
for key, value in person.items():
print(f"{key}: {value}")
name: 小明
age: 18
city: 北京

break 和 continue:控制循环的走向

有时候你不需要跑完所有循环,想提前退出——用 break。有时候你不想处理某个特定情况,跳过它——用 continue

# 找出第一个能被 3 整除的数,找到就退出
numbers = [1, 5, 7, 9, 11, 15, 21]

for num in numbers:
if num % 3 == 0:
    print(f"找到了!第一个能被 3 整除的数是 {num}")

    break
print(f"{num} 不能被 3 整除,继续找...")
1 不能被 3 整除,继续找...
5 不能被 3 整除,继续找...
7 不能被 3 整除,继续找...
找到了!第一个能被 3 整除的数是 9
# 跳过所有奇数,只处理偶数
for i in range(1, 11):
if i % 2 == 1:  # 奇数
    continue    # 跳过这次循环,直接进入下一次
print(f"{i} 是偶数")
2 是偶数
4 是偶数
6 是偶数
8 是偶数
10 是偶数

区别break 是「彻底不玩了,退出整个循环」;continue 是「这次不算,重新来过」。


🔥 实战 35 分钟:3 个递进的小项目

项目 1:成绩统计器(5 分钟)

场景:老师有一份学生成绩单,想快速知道有多少人及格、平均分是多少。

# 学生成绩列表
scores = [85, 92, 78, 60, 45, 88, 73, 95, 67, 54]

# 统计及格人数
pass_count = 0
total_score = 0

for score in scores:
if score >= 60:
    pass_count += 1
total_score += score

print(f"总人数: {len(scores)}")
print(f"及格人数: {pass_count}")
print(f"不及格人数: {len(scores) - pass_count}")
print(f"平均分: {total_score / len(scores):.2f}")

输出

总人数: 10
及格人数: 7
不及格人数: 3
平均分: 73.70

一句话解释for score in scores 遍历每个成绩,累加的同时统计及格人数。


项目 2:CSV 文件数据清洗(15 分钟)

场景:你从网上下载了一份 CSV 格式的销售数据,里面有一些无效数据(价格为 0 或负数、日期格式不对),需要清洗后再使用。

假设当前目录下有一个 sales.csv 文件,内容如下:

日期,商品,销量,单价
2024-01-01,iPhone,50,6999
2024-01-02,MacBook,20,12999
2024-01-03,AirPods,100,-1
2024-01-04,iPad,35,2999
2024-01-05,Apple Watch,0,2999
2024-01-06,AirPods,80,1999
import csv
from datetime import datetime

def清洗数据():
valid_records = []  # 存放清洗后的有效数据
error_records = []  # 存放出错的数据

# 读取 CSV 文件
with open('sales.csv', 'r', encoding='utf-8') as f:
    reader = csv.DictReader(f)

    for row in reader:
        try:
            # 验证日期格式
            date = datetime.strptime(row['日期'], '%Y-%m-%d')

            # 验证销量(必须是正整数)
            sales = int(row['销量'])
            if sales <= 0:
                raise ValueError("销量必须大于 0")

            # 验证单价(必须是正数)
            price = float(row['单价'])
            if price <= 0:
                raise ValueError("单价必须大于 0")

            # 所有验证通过,添加到有效列表
            valid_records.append({
                '日期': date.strftime('%Y-%m-%d'),
                '商品': row['商品'],
                '销量': sales,
                '单价': price,
                '销售额': sales * price
            })

        except Exception as e:
            error_records.append({
                '原始数据': row,
                '错误原因': str(e)
            })

# 输出结果
print(f"=== 数据清洗报告 ===")
print(f"有效数据: {len(valid_records)} 条")
print(f"无效数据: {len(error_records)} 条\n")

print("--- 无效数据详情 ---")
for err in error_records:
    print(f"数据: {err['原始数据']}")
    print(f"原因: {err['错误原因']}\n")

print("--- 有效数据统计 ---")
total_revenue = sum(r['销售额'] for r in valid_records)
print(f"总收入: ¥{total_revenue:,.2f}")

# 按商品汇总
product_summary = {}
for record in valid_records:
    product = record['商品']
    if product not in product_summary:
        product_summary[product] = 0
    product_summary[product] += record['销售额']

print("\n--- 商品销售额排行 ---")
for product, revenue in sorted(product_summary.items(), key=lambda x: x[1], reverse=True):
    print(f"{product}: ¥{revenue:,.2f}")

return valid_records, error_records

# 运行清洗程序
if __name__ == '__main__':
清洗数据()

输出

=== 数据清洗报告 ===
有效数据: 5 条
无效数据: 2 条

--- 无效数据详情 ---
数据: {'日期': '2024-01-03', '商品': 'AirPods', '销量': '100', '单价': '-1'}
原因: 单价必须大于 0

数据: {'日期': '2024-01-05', '商品': 'Apple Watch', '销量': '0', '单价': '2999'}
原因: 销量必须大于 0

--- 有效数据统计 ---
总收入: ¥1,399,870.00

--- 商品销售额排行 ---
MacBook: ¥259,980.00
iPhone: ¥349,950.00
AirPods: ¥159,920.00
iPad: ¥104,965.00

一句话解释csv.DictReader 帮我们逐行读取数据,用 for 遍历每一行并验证,不符合条件就跳过或记录错误。


项目 3:待办事项管理小工具(15 分钟)

场景:做一个命令行待办清单,可以添加任务、查看任务、完成任务、删除任务。数据保存在本地文件里。

import json
import os
from datetime import datetime

TODO_FILE = 'todos.json'

def 加载任务():
"""从文件加载待办事项,如果文件不存在就返回空列表"""
if os.path.exists(TODO_FILE):
    with open(TODO_FILE, 'r', encoding='utf-8') as f:
        return json.load(f)
return []

def 保存任务(todos):
"""保存待办事项到文件"""
with open(TODO_FILE, 'w', encoding='utf-8') as f:
    json.dump(todos, f, ensure_ascii=False, indent=2)

def 显示任务(todos):
"""显示所有任务"""
if not todos:
    print("📝 暂无待办事项,添加一个吧!")
    return

print(f"\n📋 共 {len(todos)} 个待办事项:")
print("-" * 50)

for index, todo in enumerate(todos, 1):
    status = "✅" if todo['done'] else "⬜"
    priority_tag = {
        'high': '🔴',
        'medium': '🟡',
        'low': '🟢'
    }.get(todo.get('priority', 'medium'), '🟡')

    print(f"{index}. {status} {priority_tag} {todo['title']}")
    if todo.get('created'):
        print(f"   创建于: {todo['created']}")
print("-" * 50)

def 添加任务(todos, title, priority='medium'):
"""添加新任务"""
todo = {
    'title': title,
    'done': False,
    'priority': priority,
    'created': datetime.now().strftime('%Y-%m-%d %H:%M')
}
todos.append(todo)
print(f"✅ 已添加: {title}")

def 完成任务(todos, index):
"""标记任务为完成"""
if 1 <= index <= len(todos):
    todos[index - 1]['done'] = True
    print(f"✅ 已完成: {todos[index - 1]['title']}")
else:
    print("❌ 无效的任务编号")

def 删除任务(todos, index):
"""删除任务"""
if 1 <= index <= len(todos):
    removed = todos.pop(index - 1)
    print(f"🗑️ 已删除: {removed['title']}")
else:
    print("❌ 无效的任务编号")

def 主菜单():
"""显示主菜单"""
print("\n" + "=" * 50)
print("📌  待办事项管理器")
print("=" * 50)
print("1. 查看所有任务")
print("2. 添加新任务")
print("3. 完成任务")
print("4. 删除任务")
print("5. 退出")
print("=" * 50)

def main():
todos = 加载任务()
print("👋 欢迎使用待办事项管理器!")

while True:
    主菜单()
    choice = input("请选择操作 (1-5): ").strip()

    if choice == '1':
        显示任务(todos)
        保存任务(todos)

    elif choice == '2':
        title = input("请输入任务内容: ").strip()
        if title:
            print("优先级: 1-高 2-中(默认) 3-低")
            p_choice = input("请选择优先级: ").strip()
            priority_map = {'1': 'high', '3': 'low'}
            priority = priority_map.get(p_choice, 'medium')
            添加任务(todos, title, priority)
            保存任务(todos)
        else:
            print("❌ 任务内容不能为空")

    elif choice == '3':
        显示任务(todos)
        if todos:
            index = input("请输入要完成的任务编号: ").strip()
            if index.isdigit():
                完成任务(todos, int(index))
                保存任务(todos)

    elif choice == '4':
        显示任务(todos)
        if todos:
            index = input("请输入要删除的任务编号: ").strip()
            if index.isdigit():
                删除任务(todos, int(index))
                保存任务(todos)

    elif choice == '5':
        保存任务(todos)
        print("👋 再见!数据已保存。")
        break

    else:
        print("❌ 无效选择,请输入 1-5")

if __name__ == '__main__':
main()

预期输出(交互示例)

👋 欢迎使用待办事项管理器!

==================================================
📌  待办事项管理器
==================================================
1. 查看所有任务
2. 添加新任务
3. 完成任务
4. 删除任务
5. 退出
==================================================
请选择操作 (1-5): 2
请输入任务内容: 完成 Python 循环章节的学习
优先级: 1-高 2-中(默认) 3-低
请选择优先级: 1
✅ 已添加: 完成 Python 循环章节的学习

==================================================
📌  待办事项管理器
==================================================
1. 查看所有任务
2. 添加新任务
3. 完成任务
4. 删除任务
5. 退出
==================================================
请选择操作 (1-5): 1

📋 共 1 个待办事项:
--------------------------------------------------
1. ⬜ 🔴 完成 Python 循环章节的学习
建于: 2024-01-15 14:30
--------------------------------------------------

一句话解释:用 while True 保持程序运行,用 for 遍历显示任务列表,所有数据存在 JSON 文件里。


💪 进阶 20 分钟:常见坑 + 性能小贴士

坑 1:修改列表时不要用 for 遍历

# ❌ 错误示例:一边遍历一边删除,可能漏掉元素
nums = [1, 2, 3, 4, 5, 6]
for n in nums:
if n % 2 == 0:
    nums.remove(n)
print(nums)  # [1, 3, 5],漏掉了 4
# ✅ 正确做法:遍历副本
nums = [1, 2, 3, 4, 5, 6]
for n in nums[:]:  # nums[:] 是副本,不影响原列表
if n % 2 == 0:
    nums.remove(n)
print(nums)  # [1, 3, 5],正确删除所有偶数

坑 2:while 循环一定要有退出条件

# ❌ 死循环示例
# n = 1
# while n > 0:
#     print(n)
#     n += 1  # 永远不会小于等于 0,会无限打印
# ✅ 正确做法:确保有退出条件
n = 1
while n <= 10:
print(n)
n += 1

坑 3:range 容易搞错边界

# ❌ 常见错误:以为 range(10) 会包含 10
for i in range(10):
print(i)  # 只打印 0-9,不包含 10
# ✅ 正确做法:想包含 10 就用 range(11)
for i in range(11):
print(i)  # 打印 0-10

坑 4:字符串拼接用 + 很低效

# ❌ 低效做法:每次循环都创建新字符串
result = ""
for i in range(1000):
result += str(i)
# ✅ 高效做法:用列表 + join
parts = []
for i in range(1000):
parts.append(str(i))
result = "".join(parts)

坑 5:忘记缩进

# ❌ 错误示例:缩进不一致或缺少缩进
# for i in range(5):
# print(i)  # 缺少缩进,会报错
# ✅ 正确做法:确保循环体有 4 个空格缩进
for i in range(5):
print(i)  # 正确缩进

性能小贴士:能用列表推导式就别用 for 循环

列表推导式是 Python 的特色,写法简洁而且速度更快:

# 普通 for 循环(3 行)
squares = []
for i in range(10):
squares.append(i ** 2)

# 列表推导式(1 行,效果一样)
squares = [i ** 2 for i in range(10)]

调试技巧:用 enumerate 同时拿到索引和值

fruits = ["苹果", "香蕉", "橙子"]

# ❌ 笨办法:手动维护索引
for i in range(len(fruits)):
print(f"{i}: {fruits[i]}")

# ✅ 优雅办法:用 enumerate
for i, fruit in enumerate(fruits, start=1):  # start=1 让索引从 1 开始
print(f"{i}: {fruit}")

✏️ 练习题

练习 1(2 分钟):改数字,找规律

  • 输入:把项目 1 的及格线从 60 改成 90
  • 预期输出:新的及格人数和不及格人数
  • 提示:只改一个数字就行

练习 2(3 分钟):加个判断

  • 输入:在练习 1 基础上,统计 90 分以上的「优秀生」人数
  • 预期输出:额外打印「优秀生人数: X」
  • 提示:用一个新的 if score >= 90 判断

练习 3(10 分钟):处理新数据

  • 输入:有一份新的成绩单 [88, 45, 92, 33, 76, 61, 55],找出所有不及格的分数
  • 预期输出:[45, 33, 55] 或类似的列表
  • 提示:用 for 遍历 + if 判断,把不及格的加入新列表

练习 4(15 分钟):串起来

  • 输入:把练习 3 的逻辑封装成一个函数 找出不及格(成绩列表),并调用它
  • 预期输出:调用函数返回正确结果
  • 提示:函数定义用 def 找出不及格(scores):,最后 return 结果

练习 5(5 分钟):看图找 bug

  • 输入:下面这段代码的运行结果是什么?
nums = [1, 2, 3, 4, 5]
for num in nums:
if num == 3:
    break
print(num)
  • 预期输出:打印 1 和 2,遇到 3 就 break 退出
  • 提示:想想 break 的作用是什么

作业:做一个「个人预算记录工具」

需求描述:做一个命令行工具,帮助用户记录每天的消费支出,并能按月统计。

功能点
1. 添加支出(金额、类别、备注、日期)
2. 查看所有支出记录
3. 按月统计总支出
4. 找出支出最高的类别

加分项
1. 支持数据导出为 CSV 文件
2. 添加「预算提醒」功能(当月支出超过设定额度时警告)

验收标准
- 能添加、查看支出记录
- 能按月统计
- 数据保存到本地文件,程序重启后不丢失
- 代码有适当注释

提交方式:评论区贴代码或 GitHub 链接


📚 总结

这一章我们学了 3 个核心点
1. for 循环:适合遍历已知的数据集(列表、字符串、字典)
2. while 循环:适合不知道要循环多少次、靠条件判断退出的场景
3. 控制流break 提前退出,continue 跳过当前 iteration

延伸学习资源
- 官方文档:Python 官方文档 - 循环
- 视频推荐:B 站小甲鱼《零基础入门学习 Python》第 11-12 集
- 书籍:《Python编程:从入门到实践》第 4 章「if 语句和循环」


你在工作中有没有遇到过「这件事明明很简单,却要手动做很多遍」的情况? 比如每周要整理一次数据报表、每月要汇总一次考勤记录。评论区聊聊,下一章我们要学的「函数」就是来解决这个问题的——把重复的代码封装起来,下次一键调用

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。