← 返回总目录

Day 10 — 循环(for/while)

Python基础 795 行 2 个可运行脚本

Day 010 — 循环(for/while)

"循环让计算机从重复劳动中解放——这恰恰是编程最本质的价值"


目录

  1. 为什么需要循环?
  2. for 循环与迭代器原理
  3. range() 函数详解
  4. break / continue / else 控制流
  5. while 循环与无限循环
  6. 循环嵌套
  7. 实战:九九乘法表
  8. 实战:菱形打印
  9. 思考题

1. 为什么需要循环?

1.1 没有循环的世界

假设你要打印 1 到 5 的数字:

python
print(1)
print(2)
print(3)
print(4)
print(5)

如果要打印 1 到 1000 呢?手写 1000 行 print()?这就是循环要解决的问题。

1.2 循环的本质

循环(Loop) 的核心思想是:同一段逻辑,作用于不同的数据。

┌─────────────────────────────────────────────────┐
│                   循环的本质                       │
│                                                   │
│   数据流: [a, b, c, d, ...]                       │
│                    │                              │
│                    ▼                              │
│    ┌─────────────────────────────┐                │
│    │   x = 数据流中下一个元素      │                │
│    │   对 x 执行统一操作          │                │
│    │   继续下一个                 │                │
│    └─────────────────────────────┘                │
│                    │                              │
│                    ▼                              │
│   结果: 处理(a), 处理(b), 处理(c), ...            │
└─────────────────────────────────────────────────┘

1.3 循环 = 迭代器 + 条件判断

Python 中几乎所有循环都可以理解为:

循环 = 迭代器(数据来源)+ 条件判断(何时停止)+ 循环体(做什么)

2. for 循环与迭代器原理

2.1 基本语法

python
for 变量 in 可迭代对象:
    循环体

执行流程:

     ┌──────────────┐
     │  可迭代对象    │
     │  [1,2,3,4,5] │
     └──────┬───────┘
            │
            ▼
     ┌──────────────┐
     │ 调用 iter()  │ ← 获取迭代器
     │ 得到迭代器    │
     └──────┬───────┘
            │
            ▼
     ┌──────────────┐
     │ 调用 next()  │ ← 每次迭代取一个元素
     │ 取出一个元素  │
     └──────┬───────┘
            │
     ┌──────▼───────┐
     │ 赋值给变量    │  ← 变量 = 取出的元素
     └──────┬───────┘
            │
            ▼
     ┌──────────────┐
     │ 执行循环体    │  ← 对这个元素执行操作
     └──────┬───────┘
            │
            ▼
     ┌──────────────┐
     │ 还有元素吗?   │───是──→ 继续 next()
     └──────┬───────┘
            │ 否
            ▼
     ┌──────────────┐
     │ 捕获 StopIteration │
     │ 循环结束       │
     └──────────────┘

2.2 迭代器协议(核心原理)

Python 中的 for 循环背后是 迭代器协议(Iterator Protocol),这是理解循环的关键:

概念 说明 魔术方法
可迭代对象(Iterable) 可以迭代的对象(列表、字符串、文件等) __iter__()
迭代器(Iterator) 负责逐个返回元素的对象 __iter__() + __next__()
迭代 遍历元素的过程 —

手动模拟 for 循环:

python
# Python 内部 for 循环的等价实现
fruits = ["苹果", "香蕉", "橘子"]

# 1. 获取迭代器
iterator = iter(fruits)  # 等价于 fruits.__iter__()

# 2. 不断调用 next() 直到 StopIteration
while True:
    try:
        fruit = next(iterator)  # 等价于 iterator.__next__()
        print(f"吃到: {fruit}")
    except StopIteration:
        break  # 没有更多元素了

# 输出:
# 吃到: 苹果
# 吃到: 香蕉
# 吃到: 橘子

2.3 哪些是可迭代对象?

几乎所有 Python 容器都是可迭代的:

python
# 可迭代对象一览
iterable_list = [
    [1, 2, 3],          # 列表
    (1, 2, 3),          # 元组
    {1, 2, 3},          # 集合
    {"a": 1, "b": 2},   # 字典(默认迭代 key)
    "hello",            # 字符串
    range(5),           # range 对象
    open("file.txt"),   # 文件(按行迭代)
    (x for x in range(3)),  # 生成器表达式
]

为什么字符串是可迭代的?

python
for char in "Python":
    print(char, end=" ")
# 输出: P y t h o n

字符串本质上是字符的序列,每个字符是一个独立的元素,所以可以迭代。

为什么整数不可迭代?

python
# for i in 42:  # ❌ TypeError: 'int' object is not iterable
# 整数不是容器,不包含元素序列,所以不可迭代

2.4 enumerate() — 同时获取索引和值

当需要 索引 + 元素 时:

python
fruits = ["苹果", "香蕉", "橘子"]

# 不优雅的方式
for i in range(len(fruits)):
    print(i, fruits[i])

# 优雅的方式
for i, fruit in enumerate(fruits):
    print(i, fruit)

# 还可以指定起始编号
for i, fruit in enumerate(fruits, start=1):
    print(f"第{i}个水果: {fruit}")

2.5 zip() — 并行迭代多个序列

python
names = ["张三", "李四", "王五"]
scores = [92, 85, 78]

for name, score in zip(names, scores):
    print(f"{name}: {score}分")

# 输出:
# 张三: 92分
# 李四: 85分
# 王五: 78分

zip() 的工作原理:

names:    "张三"   "李四"   "王五"
           │        │        │
scores:    92       85       78
           │        │        │
zip():  ("张三",92) ("李四",85) ("王五",78)

3. range() 函数详解

3.1 range() 的三种调用方式

python
range(stop)           # 0 到 stop-1
range(start, stop)     # start 到 stop-1
range(start, stop, step)  # start 到 stop-1,步长为 step

3.2 参数详解

参数 类型 必需? 默认值 说明
start int 否 0 起始值(包含)
stop int 是 — 结束值(不包含)
step int 否 1 步长(可以为负数)

3.3 代码示例

python
# 形式 1: range(stop)
print(list(range(5)))     # [0, 1, 2, 3, 4]

# 形式 2: range(start, stop)
print(list(range(2, 7)))  # [2, 3, 4, 5, 6]

# 形式 3: range(start, stop, step)
print(list(range(0, 10, 2)))   # [0, 2, 4, 6, 8]
print(list(range(10, 0, -2)))  # [10, 8, 6, 4, 2]

3.4 为什么 range() 不是列表?

python
r = range(1000000)        # 瞬间完成
l = list(range(1000000))  # 占用大量内存

print(type(r))   # <class 'range'>
print(sys.getsizeof(r))   # 48 字节(无论多大范围)
print(sys.getsizeof(l))   # 超过 8MB

关键原理:惰性求值(Lazy Evaluation)

range(1, 1000000, 2)   ← 不生成所有数字,只存储三个值

┌─────────────────────────────────┐
│          range 对象              │
│                                 │
│  start = 1     (起始值)       │
│  stop  = 1000000(终止值)      │
│  step  = 2     (步长)         │
│                                 │
│  内存占用: 固定 48 字节          │
└─────────────────────────────────┘
         │
         ▼ 迭代时按需计算
    1 → 3 → 5 → 7 → ... → 999999

range() 存储的是 计算公式 而不是所有值。只有在迭代时,才按需计算下一个值。这就是为什么它叫 "惰性"(Lazy)。

3.5 反向迭代

python
# 方式 1: step 为负数
for i in range(5, 0, -1):
    print(i, end=" ")  # 5 4 3 2 1

print()

# 方式 2: reversed()
for i in reversed(range(1, 6)):
    print(i, end=" ")  # 5 4 3 2 1

4. break / continue / else 控制流

循环控制语句让循环不再是"一根筋",可以灵活地 提前结束 或 跳过。

4.1 break — 完全退出循环

     ┌───────────────────┐
     │  for x in data:   │
     └───────┬───────────┘
             │
             ▼
     ┌───────────────┐
     │  条件满足吗?   │──是──→ break ──→ 跳出循环
     └───────┬───────┘
             │ 否
             ▼
     ┌───────────────┐
     │  执行循环体     │
     └───────┬───────┘
             │
             ▼
        回到循环开始
python
# 查找第一个负数
numbers = [3, 7, 1, -5, 9, -2, 4]

for num in numbers:
    if num < 0:
        print(f"找到第一个负数: {num}")
        break  # 找到就停止,不再继续
    print(f"处理: {num}")

# 输出:
# 处理: 3
# 处理: 7
# 处理: 1
# 找到第一个负数: -5

4.2 continue — 跳过当前迭代

     ┌───────────────────┐
     │  for x in data:   │
     └───────┬───────────┘
             │
             ▼
     ┌───────────────┐
     │  条件满足吗?   │──是──→ continue ──→ 回到循环开始
     └───────┬───────┘
             │ 否
             ▼
     ┌───────────────┐
     │  执行循环体     │
     └───────┬───────┘
             │
             ▼
        回到循环开始
python
# 只处理正数,跳过负数
numbers = [3, -1, 7, -5, 9, -2]

for num in numbers:
    if num < 0:
        continue  # 跳过负数
    print(f"处理正数: {num}")

# 输出:
# 处理正数: 3
# 处理正数: 7
# 处理正数: 9

4.3 break vs continue vs 普通结束

                 ┌─────────────┐
                 │  循环开始    │
                 └──────┬──────┘
                        │
                        ▼
            ┌─────────────────────┐
            │ 还有元素吗? / 条件      │──否──→ [else 子句] → 结束
            └─────────┬───────────┘
                      │ 是
                      ▼
            ┌─────────────────────┐
            │ 执行循环体           │
            └─────────┬───────────┘
                      │
            ┌─────────▼───────────┐
            │                     │
       ┌────▼────┐         ┌─────▼─────┐
       │ break?  │         │ continue? │
       └────┬────┘         └─────┬─────┘
            │ 是                  │ 是
            ▼                    ▼
    ┌──────────────┐    ┌──────────────────┐
    │ 跳出循环      │    │ 跳过剩余循环体    │
    │ 不执行 else   │    │ 回到条件判断      │
    └──────────────┘    └──────────────────┘

4.4 for...else — 循环正常结束才执行

这是 Python 独有的特性,初学者容易忽略,但在某些场景下非常有用。

python
# 场景:判断一个数是否是质数
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            print(f"{n} = {i} × {n//i}")
            break
    else:
        # 只有循环没有被 break 时才会执行
        print(f"{n} 是质数!")
        return True
    return False

is_prime(17)  # 17 是质数!
is_prime(15)  # 15 = 3 × 5

else 的执行条件:

循环结束方式 else 是否执行?
正常迭代完毕(无 break) ✅ 是
被 break 中断 ❌ 否
从未进入循环(空序列) ✅ 是
循环中抛出异常 ❌ 否
python
# else 在从未进入的循环中也会执行
for x in []:
    print("这行不会打印")
else:
    print("但 else 会执行!")

# 输出: 但 else 会执行!

4.5 while...else 同理

python
count = 0
while count < 3:
    print(count)
    count += 1
else:
    print("循环正常结束!")
# 输出: 0 1 2 循环正常结束!

count = 0
while count < 3:
    print(count)
    count += 1
    if count == 2:
        break
else:
    print("不会执行")
# 输出: 0 1

5. while 循环与无限循环

5.1 基本语法

python
while 条件表达式:
    循环体

执行流程:

     ┌───────────┐
     │  条件判断   │──否──→ 结束循环
     └─────┬─────┘
           │ 是
           ▼
     ┌───────────┐
     │  执行循环体  │
     └─────┬─────┘
           │
           ▼
     回到条件判断

5.2 什么时候用 while 而不是 for?

场景 推荐 原因
明确知道迭代次数 for 简洁、安全
不知道多少次,只知道条件 while 灵活
遍历容器元素 for Pythonic
循环可能永远不执行 for 空序列=0次
至少执行一次 while + 标志位 易控制

5.3 无限循环(Infinite Loop)

python
# 这是无限循环——危险!
# while True:
#     print("永远停不下来...")

无限循环的两种情况:

  1. 意外无限循环(Bug)—— 要避免
  2. 故意无限循环(设计)—— 配合 break 使用

意外无限循环示例:

python
# ❌ 忘记更新计数器
count = 0
while count < 10:
    print(count)
    # 忘记写 count += 1,count 永远等于 0

# ❌ 条件写错
x = 10
while x > 0:  # 应该是 x > 0
    print(x)
    x += 1  # 应该递减,结果递增了

# ❌ 浮点数精度问题
x = 0
while x != 1.0:  # 浮点数精确相等很危险!
    x += 0.1

故意无限循环(正确的打开方式):

python
# ✅ 用户输入验证
while True:
    user_input = input("请输入 'quit' 退出: ")
    if user_input == 'quit':
        break
    print(f"你输入了: {user_input}")

5.4 while 实战:猜数字游戏

python
import random

target = random.randint(1, 100)
attempts = 0

print("🎯 猜数字游戏!范围: 1-100")

while True:
    try:
        guess = int(input("请输入你的猜测: "))
        attempts += 1

        if guess < target:
            print("📈 太低了!")
        elif guess > target:
            print("📉 太高了!")
        else:
            print(f"🎉 恭喜!你猜了 {attempts} 次!")
            break
    except ValueError:
        print("❌ 请输入有效数字!")

6. 循环嵌套

6.1 什么是循环嵌套?

循环嵌套就是 循环里面再套循环。外层循环每执行一次,内层循环执行完整一轮。

外循环 (i = 1)  ─→  内循环 (j = 1, 2, 3)
                   内循环结束
外循环 (i = 2)  ─→  内循环 (j = 1, 2, 3)
                   内循环结束
外循环 (i = 3)  ─→  内循环 (j = 1, 2, 3)
                   内循环结束

6.2 执行次数计算

python
# 嵌套循环的总执行次数 = 外循环次数 × 内循环次数
for i in range(3):      # 执行 3 次
    for j in range(4):  # 每次执行 4 次
        print(f"({i},{j})", end=" ")
    print()

# 总共打印了 3 × 4 = 12 个坐标
# (0,0) (0,1) (0,2) (0,3)
# (1,0) (1,1) (1,2) (1,3)
# (2,0) (2,1) (2,2) (2,3)

6.3 用嵌套循环绘制图形

这是理解嵌套循环最好的方式:

python
# 绘制矩形
for i in range(4):
    for j in range(8):
        print("*", end="")
    print()

# 输出:
# ********
# ********
# ********
# ********
python
# 绘制直角三角形
for i in range(1, 6):
    for j in range(i):
        print("*", end="")
    print()

# 输出:
# *
# **
# ***
# ****
# *****

7. 实战:九九乘法表

7.1 基础版本

python
print("=== 九九乘法表 ===")

for i in range(1, 10):          # 外层:被乘数 1-9
    for j in range(1, i + 1):   # 内层:乘数 1-i(不重复)
        print(f"{j}×{i}={i*j:2d}", end="  ")
    print()  # 换行

输出效果:

=== 九九乘法表 ===
1×1= 1
1×2= 2  2×2= 4
1×3= 3  2×3= 6  3×3= 9
1×4= 4  2×4= 8  3×4=12  4×4=16
1×5= 5  2×5=10  3×5=15  4×5=20  5×5=25
1×6= 6  2×6=12  3×6=18  4×6=24  5×6=30  6×6=36
1×7= 7  2×7=14  3×7=21  4×7=28  5×7=35  6×7=42  7×7=49
1×8= 8  2×8=16  3×8=24  4×8=32  5×8=40  6×8=48  7×8=56  8×8=64
1×9= 9  2×9=18  3×9=27  4×9=36  5×9=45  6×9=54  7×9=63  8×9=72  9×9=81

7.2 理解为什么内层 range 到 i+1

i=1:  j 从 1 到 1   →   1×1
i=2:  j 从 1 到 2   →   1×2  2×2
i=3:  j 从 1 到 3   →   1×3  2×3  3×3
...

j 的范围是 range(1, i+1),保证每行只打印 i 个算式,且只打印下三角部分(j ≤ i),避免重复(如 1×2 和 2×1 是同一个算式)。

7.3 完整实战代码(含多种变体)

详见 code/01-multiplication-table.py


8. 实战:菱形打印

8.1 问题分析

打印一个菱形(高度为 7 行):

   *
  ***
 *****
*******
 *****
  ***
   *

关键观察:
- 菱形由 空格 和 星号 组成
- 上半部分:空格递减,星号递增
- 下半部分:空格递增,星号递减
- 空格和星号的数量关系:上三角 空格 = n - i, 星号 = 2*i - 1
- 下三角:空格 = i, 星号 = 2*(n-i) - 1

8.2 打印过程分解

行号  空格数  星号数  效果
──────────────────────────
1      3       1        ___*
2      2       3        __***
3      1       5        _*****
4      0       7        *******
5      1       5        _*****
6      2       3        __***
7      3       1        ___*

8.3 代码实现

python
n = 4  # 上半部分行数

# 上半部分
for i in range(1, n + 1):
    # 打印空格
    for j in range(n - i):
        print(" ", end="")
    # 打印星号
    for j in range(2 * i - 1):
        print("*", end="")
    print()

# 下半部分
for i in range(1, n):
    # 打印空格
    for j in range(i):
        print(" ", end="")
    # 打印星号
    for j in range(2 * (n - i) - 1):
        print("*", end="")
    print()

详见 code/02-diamond-printing.py


9. 思考题

  1. 迭代器与索引:为什么 Python 推荐用 for item in list 而不是 for i in range(len(list))?两种方式的性能差异是什么?当需要修改列表元素时应该怎么做?

  2. break vs return:在函数中使用循环时,break 和 return 有什么区别?什么时候用哪个?

  3. 无限循环的哲学:为什么有些程序故意用 while True 而不是设定有限条件?在网络服务器、游戏主循环等场景中,这种模式有什么好处?

  4. for...else 的实际用途:除了判断质数,你能想到 for...else 还有什么实际应用场景?例如在搜索、验证等场景中。

  5. 嵌套循环的优化:下面的两段代码功能相同,为什么 O(n²) 版本比 O(n²/2) 版本执行更多次?

python
# 版本 A: 1+2+...+n = n(n+1)/2 次
for i in range(n):
    for j in range(i, n):
        ...

# 版本 B: n × n = n² 次
for i in range(n):
    for j in range(n):
        ...