循环语句 for 与 while

条件语句让程序”做选择”,循环语句让程序”重复干活”。上一篇 条件语句 if/elif/else 讲完了选择,这一篇要讲 Python 里两种循环:forwhile。Python 的 for 循环跟 C/Java 完全不一样——它没有”变量 i 从 0 到 n”这种写法,而是”取出一个可迭代对象里的每个元素”。理解这个差异,你写循环的水平能直接跨一个台阶。

一、for:遍历可迭代对象

for 的基础形式:

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

举例:

1
2
3
4
5
6
7
8
for name in ["咖飞", "小王", "小李"]:
print(f"你好,{name}")

for ch in "Python":
print(ch)

for key in {"a": 1, "b": 2}: # 遍历字典默认遍历 key
print(key)

Python 的 for 不是”计数器循环”,它是”迭代器循环”。任何可迭代对象都能放在 in 后面:列表、元组、字典、集合、字符串、文件、生成器……

二、range:生成数字序列

想让 for 循环 n 次?用 range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for i in range(5):
print(i)
# 0 1 2 3 4

for i in range(1, 6): # [1, 6)
print(i)
# 1 2 3 4 5

for i in range(0, 10, 2): # start=0, stop=10, step=2
print(i)
# 0 2 4 6 8

for i in range(10, 0, -1):
print(i)
# 10 9 8 7 6 5 4 3 2 1

range(n) 不会立即生成一个 n 元素列表,而是懒生成——只在迭代时按需产生下一个数字。range(10 ** 9) 也几乎不占内存。

如果你确实要一个列表:list(range(10))[0, 1, ..., 9]

三、enumerate:需要索引时的正确姿势

新手写法(不推荐):

1
2
3
names = ["咖飞", "小王", "小李"]
for i in range(len(names)):
print(i, names[i])

Pythonic 写法

1
2
for i, name in enumerate(names):
print(i, name)

enumerate 会同时给你索引和元素。可以指定起始编号:

1
2
for i, name in enumerate(names, start=1):
print(f"第 {i} 名:{name}")

四、zip:并行遍历多个序列

1
2
3
4
5
names = ["咖飞", "小王"]
ages = [3, 20]

for name, age in zip(names, ages):
print(f"{name}{age} 岁")

zip 会以最短的序列为准,多出来的会被丢弃:

1
list(zip([1, 2, 3], ["a", "b"]))    # [(1, 'a'), (2, 'b')]

想以最长为准并补默认值:itertools.zip_longest

五、遍历字典

1
2
3
4
5
6
7
8
9
10
11
12
13
score = {"张三": 90, "李四": 85, "王五": 78}

# 只要 key
for name in score:
print(name)

# 要 key 也要 value
for name, s in score.items():
print(name, s)

# 只要 value
for s in score.values():
print(s)

六、while:条件循环

1
2
3
4
count = 0
while count < 5:
print(count)
count += 1

while 的关键:每次迭代前判断条件,为真才进入循环体。所以你要确保条件能变为假,否则死循环。

死循环有时是故意的——比如服务端:

1
2
3
4
5
while True:
request = get_next_request()
if request is None:
break
handle(request)

想优雅地”读到 EOF 结束”:

1
2
while (line := input()):     # 海象运算符
process(line)

七、for 还是 while?

  • 知道要循环的对象/次数 → for;
  • 循环退出取决于某个条件 → while。
1
2
3
4
5
6
7
8
9
10
# for:处理一批固定数据
for user in users:
send_email(user)

# while:一直读,读到"quit"退出
while True:
cmd = input()
if cmd == "quit":
break
handle(cmd)

八、break / continue / else

  • break:立刻退出循环;
  • continue:跳过本次迭代,进入下一次;
  • else循环正常结束(没被 break)时执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
# 找第一个偶数
for n in [1, 3, 5, 4, 7]:
if n % 2 == 0:
print(f"找到 {n}")
break
else:
print("没找到")

# continue:跳过负数
for n in [3, -1, 5, -2, 8]:
if n < 0:
continue
print(n)

for-else / while-else 是 Python 特色,其他语言基本没有。注意 else 不是配对 if 的,而是配对 for/while 的

1
2
3
4
5
6
7
8
# 判断质数
n = 17
for i in range(2, n):
if n % i == 0:
print(f"{n} 不是质数")
break
else:
print(f"{n} 是质数")

九、循环控制的深入讲解

关于 break、continue、else 的更多细节和陷阱,我们下一篇 循环控制:break、continue、else 会单独讲。

十、嵌套循环

1
2
3
4
5
# 九九乘法表
for i in range(1, 10):
for j in range(1, i + 1):
print(f"{j}*{i}={i*j}", end="\t")
print()

跳出嵌套循环break 只跳出最近的一层。要跳多层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 方式 1:用 flag
found = False
for row in matrix:
for cell in row:
if cell == target:
found = True
break
if found:
break

# 方式 2:抽成函数,用 return
def find(matrix, target):
for row in matrix:
for cell in row:
if cell == target:
return True
return False

方式 2 更 Pythonic,几乎所有多层循环都可以用”抽函数 + return”替代 flag。

十一、循环的效率优化

1. 避免在循环里重复计算

1
2
3
4
5
6
7
# ❌ 每次循环都调用 len()
for i in range(len(a)):
...

# ✅ 更好的是能不用索引就不用
for x in a:
...

2. 用 in / set 而不是循环查找

1
2
3
4
5
6
7
8
9
# ❌ O(n)
def contains(items, target):
for x in items:
if x == target:
return True
return False

# ✅ 一步搞定
target in items

3. 用生成器代替中间列表

1
2
3
4
5
# ❌ 生成 100 万个中间元素
sum([x*x for x in range(10**6)])

# ✅ 生成器表达式,边算边加
sum(x*x for x in range(10**6))

十二、常用循环模式

累加

1
2
3
4
total = 0
for x in numbers:
total += x
# 等价:total = sum(numbers)

收集

1
2
3
4
5
result = []
for x in items:
if x > 0:
result.append(x * 2)
# 等价:result = [x*2 for x in items if x > 0](列表推导式,第 15 篇讲)

分组计数

1
2
3
4
counter = {}
for w in words:
counter[w] = counter.get(w, 0) + 1
# 等价:from collections import Counter; Counter(words)

早期退出

1
2
3
4
5
def find_first_negative(nums):
for n in nums:
if n < 0:
return n
return None

十三、一个综合例子:素数筛选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# primes.py
"""筛选出 2..N 之间的所有质数(埃拉托色尼筛法)。"""

def sieve(n: int) -> list[int]:
if n < 2:
return []
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False

for i in range(2, int(n ** 0.5) + 1):
if is_prime[i]:
# 从 i*i 开始把 i 的倍数标为合数
for j in range(i * i, n + 1, i):
is_prime[j] = False

return [i for i, prime in enumerate(is_prime) if prime]


def main():
n = int(input("求 2 到 N 之间的所有质数,请输入 N:"))
primes = sieve(n)
print(f"共 {len(primes)} 个质数:")
# 每行 10 个
for i, p in enumerate(primes):
print(f"{p:5d}", end="\n" if (i + 1) % 10 == 0 else " ")
print()


if __name__ == "__main__":
main()

这段代码用到了:for、range、双层循环、break/continue/else 的思想(虽然本例没显式用)、列表推导式、enumerate、f-string 对齐格式化。

十四、常见陷阱

陷阱 1:循环中修改被遍历的列表

1
2
3
4
5
items = [1, 2, 3, 4, 5]
for x in items:
if x % 2 == 0:
items.remove(x) # 危险!索引错乱
print(items) # 可能不是你想的

正确做法:遍历副本,或者构造新列表

1
2
items = [x for x in items if x % 2]   # 保留奇数
# 或者:for x in items[:]: 遍历副本

陷阱 2:变量作用域

1
2
3
for i in range(3):
pass
print(i) # 输出 2,i 泄漏到外层作用域

Python 的 for 变量不局限在循环体内,这跟 C/Java 不同。避免依赖循环变量在循环外的值

陷阱 3:range 的性能误区

for i in range(len(a)) 这种写法性能其实还行,但可读性差。能不用索引就不用

陷阱 4:字典遍历时修改

1
2
3
4
d = {"a": 1, "b": 2}
for k in d:
if d[k] == 1:
del d[k] # RuntimeError: dictionary changed size

list(d.items()) 拷一份再遍历,或者用字典推导式重建。

陷阱 5:忘了 int() 转型

1
2
3
n = input("次数:")
for i in range(n): # TypeError,n 是字符串
...

for i in range(int(n)): 才对。

十五、小结与延伸阅读

  • for 是”遍历可迭代对象”,不是”计数器循环”;
  • 需要索引用 enumerate,并行多序列用 zip
  • while 用于”条件驱动的循环”;
  • break/continue/else 是循环控制三件套;
  • 跳出多层循环用函数 + return,别写 flag;
  • 循环中不要修改被遍历的容器;
  • 常见循环模式(累加、收集、计数、找第一个)都有标准库替代品,能不写循环就不写。

延伸阅读:

下一篇 循环控制:break、continue、else 我们要把 break/continue/else 的细节和惯用法讲透。