条件语句让程序”做选择”,循环语句让程序”重复干活”。上一篇 条件语句 if/elif/else 讲完了选择,这一篇要讲 Python 里两种循环:for 和 while。Python 的 for 循环跟 C/Java 完全不一样——它没有”变量 i 从 0 到 n”这种写法,而是”取出一个可迭代对象里的每个元素”。理解这个差异,你写循环的水平能直接跨一个台阶。
一、for:遍历可迭代对象
for 的基础形式:
举例:
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}: 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)
for i in range(1, 6): print(i)
for i in range(0, 10, 2): print(i)
for i in range(10, 0, -1): print(i)
|
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"]))
|
想以最长为准并补默认值:itertools.zip_longest。
五、遍历字典
1 2 3 4 5 6 7 8 9 10 11 12 13
| score = {"张三": 90, "李四": 85, "王五": 78}
for name in score: print(name)
for name, s in score.items(): print(name, s)
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 user in users: send_email(user)
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("没找到")
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
| found = False for row in matrix: for cell in row: if cell == target: found = True break if found: break
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
| for i in range(len(a)): ...
for x in a: ...
|
2. 用 in / set 而不是循环查找
1 2 3 4 5 6 7 8 9
| def contains(items, target): for x in items: if x == target: return True return False
target in items
|
3. 用生成器代替中间列表
1 2 3 4 5
| 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
|
收集
1 2 3 4 5
| result = [] for x in items: if x > 0: result.append(x * 2)
|
分组计数
1 2 3 4
| counter = {} for w in words: counter[w] = counter.get(w, 0) + 1
|
早期退出
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
| """筛选出 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]: 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)} 个质数:") 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]
|
陷阱 2:变量作用域
1 2 3
| for i in range(3): pass print(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]
|
用 list(d.items()) 拷一份再遍历,或者用字典推导式重建。
陷阱 5:忘了 int() 转型
1 2 3
| n = input("次数:") for i in range(n): ...
|
for i in range(int(n)): 才对。
十五、小结与延伸阅读
- for 是”遍历可迭代对象”,不是”计数器循环”;
- 需要索引用
enumerate,并行多序列用 zip;
- while 用于”条件驱动的循环”;
break/continue/else 是循环控制三件套;
- 跳出多层循环用函数 + return,别写 flag;
- 循环中不要修改被遍历的容器;
- 常见循环模式(累加、收集、计数、找第一个)都有标准库替代品,能不写循环就不写。
延伸阅读:
下一篇 循环控制:break、continue、else 我们要把 break/continue/else 的细节和惯用法讲透。