## 引言
前三篇的分支与循环解决了一个问题:程序怎么「走」;但真实程序还有一个高频需求——怎么「提前停下来」与「跳过某些步」。搜索一批数据时,找到目标就该立刻收工,继续遍历是浪费;处理脏数据时,某些记录不该处理,应该直接跳到下一条。这两件事分别由 **break** 和 **continue** 完成,而本篇的第三位主角——循环的 **else 子句**——解决的是「有没有正常走完全程」的判断,它能让许多「查找失败」「全部通过」的逻辑写得比用标志变量(flag)更干净。这三者就是「循环控制三大件」,是第三章第 5 篇,也是第 4 篇 for 与 range 的直接延续;第 6 篇嵌套与组合将在二维数据场景里继续复用它们。
读者应先掌握:第 4 篇的可迭代对象与 range 遍历、第 1 篇的分支结构与「流程如何被语句决定」的基本直觉、第二章的真值测试。本篇会用质数判断(一个数 n 若大于 1 且不是质数,必有某个因子 d 满足 2 ≤ d < n——因此从 2 一路试除到 n-1 即可判定;更省的做法是只试到 √n,留作练习)与搜索场景把三个关键字讲透,并给出一个贯穿全文的工程观念:**能用结构表达的意图,就不要用变量去记**。
## 概念与原理
### break:终止整个循环
break 的语义只有一句话:**立即终止它所在的最近一层循环,循环体剩余语句不再执行,程序从循环结束后的下一行继续**。注意两个限定词。「终止」是指整层循环都结束,不是只跳过当前这一轮——这是它和 continue 的本质区别;「最近一层」是作用域规则:嵌套循环里,内层的 break 只让内层循环终止,外层循环照常进行(遍历完整内层后继续下一个外层迭代),这个规则在嵌套场景里极其重要,第 6 篇还会遇到它。
为什么需要 break?因为「提前退出」是算法里的常态:线性搜索找到目标即返回、质数判断发现一个因子即可下结论、解析配置遇到第一个错误就该停止。如果没有 break,你只能靠「让循环条件里带上复杂布尔表达式」或「标志变量加空转」,两者都让代码变脏。break 让「循环中途改主意」成为一等表达。
### continue:跳过本轮,回到循环头
continue 的语义也是两句话:**跳过本次迭代中 continue 之后的所有语句,直接进入下一轮**。对 for 循环,「下一轮」意味着用迭代器取下一个元素;对 while 循环,「下一轮」意味着回到 while 条件处重新判断。这里藏着一个著名的坑:while 循环里,如果「让条件最终变假的语句」写在 continue 之后,continue 会把它永远跳过——循环条件永不满足,程序死循环。for 循环没有这个问题,因为「取下一个元素」是由 for 语句本身驱动、不由循环体负责。记住口诀:**while 的推进是自己负责的,continue 之前必须已经推进或保证推进会发生**。
break 与 continue 的另一层关系:它们都可以放在 if 分支里,实现「在满足某个条件时改变循环行为」——循环 + 分支的组合由此开始(这是第 6 篇的正式主题之一)。它们不能放在循环之外:在函数体或其他代码块里写 break 或 continue,会直接得到 `SyntaxError: 'break' outside loop`,这是语法层面的硬限制。
### else 子句:循环正常走完才会执行
Python 的 for 与 while 都可以带一个 else 子句,这是它区别于多数主流语言(C/Java 用标志变量)的独特设计。触发规则只有一条:**如果循环没有被 break 打断、正常执行完毕(包括一次都没进入的情况),else 块执行;一旦循环体里发生过 break,else 块就被跳过**。continue 不影响 else——continue 只是跳过本轮,循环最终还是「正常走完」的,所以 else 照常执行。注意 break 也不只是「退出」,它还隐式地宣告「本轮工作有了结论」,于是 else 的含义推而广之就是「循环从头到尾没有产生 break 结论」——这正是「查找失败」「全部通过」两类判断的标准语义。
这套设计的动机是消除标志变量。没有 else 时,「判断列表里有没有目标值」要写:声明 found = False;循环里找到就置 True 并 break;循环后 if not found。有了 else,循环本身的「结果」就能被读取,found 变量整个删掉。结构比变量更能表达意图:break 是「我找到了,快走」,else 是「从头到尾没找到」。理解这一点,for-else 就不再是「奇怪语法」,而是寻常的「循环的收尾声明」。
### 两个经典配对的直觉
质数判断是 for-else 的教科书场景:判断 n 是不是质数,试除所有可能的因子 d;只要有一个 d 能整除,break 并宣告「不是质数」;如果试完了所有 d 都没有整除,else 宣告「是质数」。这里的逻辑闭环是:**else 只在你从未 break 时执行,而未 break 恰好意味着没有因子——else 不是锦上添花,而是判断结果本身**。搜索场景同理:找到即 break,找不到就自然落入 else。记住这对搭档:break = 提前宣布结论;else = 默认结论(未被打断)。
## 操作与实现
### break 的完整行为
```python
for n in range(1, 6):
if n == 3:
break # 终止整个循环
print(n)
print("循环已结束,程序继续")
```
运行输出:
```text
1
2
循环已结束,程序继续
```
n 等于 3 时 break 立即触发,3、4、5 的 print 全部没执行,程序跳到循环后的 print。这就是「提前退出」:省下了剩余的迭代次数。
### continue 的完整行为
```python
for n in range(1, 6):
if n == 3:
continue # 跳过本轮,进入下一轮
print(n)
print("结束")
```
输出:
```text
1
2
4
5
结束
```
3 被跳过,其余照常;循环本身正常走完(没有 break),所以最后的打印照常执行。continue 与 break 的区别在此一目了然:一个跳过本轮继续,一个终结整层。
### while 循环里的 continue:推进语句的位置
```python
i = 0
while i < 6:
i += 1 # 把推进放在 continue 之前,绝不跳过
if i == 3:
continue
print(i)
else:
print("未被 break 打断,else 正常执行")
```
输出:
```text
1
2
4
5
6
未被 break 打断,else 正常执行
```
这段同时演示了两件事:其一,`i += 1` 写在循环体最前面,continue 跳过的只是 print,推进不受影响,不会死循环;其二,这个 while 全程没有 break,最后走了 else。把它和下面的「陷阱一」的例子对比,你会理解推进顺序为什么是 while + continue 的生命线。
### 嵌套作用域:break 只跳最近一层
```python
for i in range(3):
for j in range(4):
if j == 2:
break # 只跳出内层循环
print(i, j)
```
输出:
```text
0 0
0 1
1 0
1 1
2 0
2 1
```
每行 `j` 只到 1:内层循环在 j == 2 时被终止;但外层 i 完整走了 0、1、2 三轮——外层没有被波及。想「一次跳出所有层」需要连坐手段(标志变量或函数 return),第 6 篇的提前退出设计会专门处理。
### for-else:质数判断
```python
for n in range(2, 20):
for d in range(2, n): # 从 2 试除到 n-1
if n % d == 0:
break # 找到因子:n 不是质数
else:
print(n, "是质数")
```
输出:
```text
2 是质数
3 是质数
5 是质数
7 是质数
11 是质数
13 是质数
17 是质数
19 是质数
```
注意 n = 2 时内层 `range(2, 2)` 是空序列,循环一次都没进,内层 else 依然执行——这印证了规则:**空循环(一次都没跑)也算「正常走完」,else 照样触发**。第 4 篇讲过半开区间、range(2,2) 为空——两个知识在这里合流。
### for-else:搜索场景
```python
data = [4, 8, 15, 16, 23, 42]
for target in (16, 99): # 依次搜索两个目标
for v in data:
if v == target:
print("找到", target)
break
else:
print("未找到", target) # 只有从未 break 才执行
```
输出:
```text
找到 16
未找到 99
```
搜索 16 时内层 break 触发,内层 else 被跳过;搜索 99 时从头到尾没匹配,内层正常走完,else 输出「未找到」。这就是上一节说的「break = 提前宣布结论,else = 默认结论」。
### 省 flag:两种写法的对比
先看必须用标志变量的 C 风格写法(Python 里也能写,但啰嗦):
```python
texts = ["hello", "hi!", "ok"]
contains_bad = False
for t in texts:
if "!" in t:
contains_bad = True
break
if contains_bad:
print("发现非法字符")
else:
print("全部合法")
```
再用 for-else 改写,标志变量整个消失:
```python
texts = ["hello", "hi!", "ok"]
for t in texts:
if "!" in t:
print("发现非法字符")
break
else:
print("全部合法")
```
两段输出相同(`发现非法字符`;把 texts 换成全合法数据则都是 `全部合法`),但第二段更短、更不容易漏掉「循环后还要再看一眼 flag」这一步。写不出 for-else 时你写的是「状态管理」,写出来之后你写的是「意图表达」——这是本轮最重要的收获。
### 工程场景:批量记录的完整性检查
数据库导入、日志序号的连续性检查是 for-else 的高频舞台:
```python
ids = [1, 2, 3, 5, 6] # 导入的记录 ID,有缺失
for expect in range(1, 7):
if expect not in ids:
print("缺失 ID:", expect)
break
else:
print("ID 连续,无缺失")
```
输出 `缺失 ID: 4`:第一个不存在的 ID 一经发现立即 break,不用把列表全查完;若 ids 补全了 4,循环自然走完,else 宣告「无缺失」。这种「找到即停 + 没找到给结论」的节奏,正是 break/else 组合的典型节拍。
## 易错点与陷阱
### 陷阱一:while + continue 死循环
这是 continue 最著名的坑:
```python
i = 0
while i < 10:
if i == 3:
continue # 死循环:i 的推进被跳过
print(i)
i += 1
```
i 停在 3 之后,每次循环都在 continue 处折返,`i += 1` 永远执行不到,条件 `i < 10` 永远为真——程序卡死(Ctrl+C 才能中断)。教训:**while 的推进语句必须放在 continue 之前,或者保证 continue 永远不会绕过推进**。for 循环没有平行问题,因为「取下一个元素」不由循环体负责。
### 陷阱二:语法错误与「break 没用对地方」
`SyntaxError: 'break' outside loop` 是新手绕不开的报错:把 break 写进 if 块但 if 不在任何循环里、或者 break 的缩进比 for 还浅。记住两层常识:其一,break/continue 只属于循环语句,不属于 if/else/def,它们写在这些块内但外层没有循环即报语法错误;其二,break 与 continue 的「最近一层」规则——内层循环里的 break 对上层循环毫无影响,若期望退出所有层,见第 6 篇的提前退出设计。
### 陷阱三:认为 for-else 的 else 属于 if,或触发条件记反
两个常见误解。其一,`for ...: if ...: ... else:` 这种缩进下,else 究竟跟谁?跟 for(与 for 同一缩进)就是循环的 else;跟 if 同一缩进则是分支的 else——两个语义完全不同,缩进差一格,行为天差地别。其二,误以为「循环体没执行过就不走 else」或「continue 会让 else 失效」:实际上空循环也走 else,continue 不阻止 else——只有 break 能阻止。判断方法很简单:你只需要回答「这个循环里有没有发生过 break」,有则 else 跳过,无则 else 执行。
### 陷阱四:忘记 break,导致「找多次」
搜索场景忘了写 break 不会报错,但会有两种微妙问题:一是找到目标后循环继续空转,浪费剩余迭代;二是在 for-else 里忘了 break,else 永远不会执行(循环总是走完),「未找到」分支失效,程序全程静默出错。写完搜索型循环,养成回头检查的习惯:**有 else 的循环,里面几乎必然有一个对应的 break**。
## 小结
break 立即终止所在最近一层循环,continue 跳过本轮回到循环头(while 里必须保证推进不受 continue 影响),else 子句只在循环未被 break 打断时执行——空循环也执行、continue 不阻止它。三者的关系可以浓缩成一句:break 是「提前宣布结论」,else 是「从头到尾没有结论可宣布」,continue 是「这一轮不算数」。以 for-else 替代 flag 变量,是「用结构表达意图」的第一次实践;质数判断与搜索是它的标准舞台。下一篇文章将把这些零件装进嵌套结构与二维数据,你会看到 break/else 在「多重循环提前退出」里继续发挥关键作用。
## 练习与思考题
1. 用 for-else 判断列表 `nums = [3, 5, 7, 11, 13]` 是否「全部为奇数」,输出判断结果;再给列表加入一个偶数验证 else 不触发。
2. 在 while 循环里统计「最近一次连续成功次数」:遍历 `results = ["ok", "ok", "fail", "ok", "ok", "ok"]`,要求遇 fail 把计数清零并 continue,最后打印计数。
3. 把质数判断扩展到输出 100 以内所有质数,并统计质数个数;思考:这个程序里内层 else 每次循环都在执行,为什么不会误判?
4. 思考题:for-else 的 else 与 if-else 的 else 在「触发条件」上有什么本质不同?如果 Python 没有循环 else,你会用哪种模式替代(写出替代代码),并说明两种写法在可读性上的差异。