本讲导览
Lesson Overview · 本章共 21 个知识点 + 9 个综合案例
前两章写的程序都是"从上到下一条道走到底",这种程序只能处理一种情况。本章给程序装上岔路口(选择)和重复器(循环)——这是"会写程序"和"只会抄程序"的分界线。
本课地图
本章知识点总览
点击节点可跳转到对应讲解位置,点击三角可展开 / 收起分支。
进度表点名的 7 项 → 本节落点
| # | 进度表点名内容 | 本页位置 |
|---|---|---|
| 1 | 双分支选择结构 | 模块一 §1.4 |
| 2 | 多分支结构选择结构 | 模块一 §1.5 / §1.6 |
| 3 | While 循环结构 | 模块二 §2.2 / §2.3 |
| 4 | for 循环结构 | 模块二 §2.4 / §2.5 |
| 5 | break、continue、else | 模块二 §2.6 / §2.7 |
| 6 | 嵌套的循环结构 | 模块二 §2.8 |
| 7 | 程序控制结构综合应用 | 模块三 |
顺序结构与选择结构
第 1 讲 · 45 分钟 · 顺序结构 · 条件表达式 · 单分支 · 双分支 · 多分支 · 嵌套 · 三元表达式
1.1 顺序结构:程序的默认形态
程序从上往下、一行一行、不跳过、不回头地执行——这就是顺序结构,它是所有程序的默认形态。分支和循环都是在这个"默认流水"上打岔:先认清默认,才知道岔在哪儿。
# 顺序结构:三条语句依次执行
r = 5
pi = 3.14159
area = pi * r * r
print("圆的面积:", area)
圆的面积: 78.53975
1.2 条件表达式:什么东西能当条件
教材原话:"所有的 Python 合法表达式(算术表达式、关系表达式、逻辑表达式等,包括单个常量、变量或函数)都可以作为条件表达式。"也就是说,if 后面不一定要写 > < ==,任何值都能当条件,Python 会把它转成 True / False。
| 值 | 当条件时 | 说明 |
|---|---|---|
0、0.0 | False | 数字"非零即真" |
| 非零数字 | True | if x % 2: 等价于 if x % 2 != 0: |
""(空串) | False | 字符串"非空即真" |
| 非空字符串 | True | 常用作"用户到底输入了没有" |
None | False | 空值 |
空列表 []、空字典 {} | False | 第 4 章还会遇到 |
print(bool(0), bool(2), bool(-1))
print(bool(""), bool("hi"))
print(bool(None), bool([]), bool([0]))
False True True False True False False True
None 与空容器为假。这一条到第 8 章 Pandas 的 dropna() 还在用。1.3 单分支选择结构:满足才做
语法:if 条件表达式: 换行缩进写语句块。三条铁律(教材原文):① Python 靠缩进表达代码层次,不是靠大括号;② 行尾冒号表示缩进开始,缩进结束表示代码块结束;③ 同一级别的语句块,缩进量必须相同。
x = int(input("输入一个数字:"))
if x % 2 == 0:
print("偶数")
print("判断结束") # 这一行已经退出 if 了,无论如何都执行
输入一个数字:8 偶数 判断结束
x = int(input("输入一个数字:"))
if x % 2 == 0:
print("偶数")
print("判断结束")
输入一个数字:7 判断结束
IndentationError(好办,报错就改);另一种是不报错、但逻辑悄悄错了(难查)。把 print("判断结束") 缩进到 if 里面,它就变成"只有偶数才打印"——程序照跑,答案错了。1.4 双分支选择结构 进度表点名
两个分支互斥且穷尽:条件成立走语句块 1,否则走语句块 2,二选一,没有第三种可能。
x = int(input())
if x % 2 == 0:
print("偶数")
else:
print("奇数")
7 奇数
else x % 2 == 1: 会直接 SyntaxError。记住:else 的含义是"以上都不成立",所以它后面永远不写条件。1.5 多分支选择结构 进度表点名
语法为 if / elif / elif / … / else,其中 else 部分可选。教材原话:"如果所有表达式都为 False,且存在 else 部分,则执行语句块 n。"
score = int(input("请输入成绩:"))
if score > 100 or score < 0:
result = "输入错误,成绩必须在 0 到 100 之间"
elif 80 <= score <= 100:
result = "A"
elif 60 <= score < 80:
result = "B"
else:
result = "C"
print("成绩等级:", result)
请输入成绩:85 成绩等级: A
# 合法性检查必须放在第一个:否则 101 会被后面的分支"接走"
for score in [101, -1, 100, 80, 79, 60, 59, 0]:
if score > 100 or score < 0:
result = "输入错误"
elif 80 <= score <= 100:
result = "A"
elif 60 <= score < 80:
result = "B"
else:
result = "C"
print("输入", score, "→", result)
输入 101 → 输入错误 输入 -1 → 输入错误 输入 100 → A 输入 80 → A 输入 79 → B 输入 60 → B 输入 59 → C 输入 0 → C
1.6 必做演示:elif 顺序陷阱 学生 100% 会踩
把宽条件写在前面,后面的严格条件就永远没有机会判断。下面把错版和正版并排跑一遍。
for score in [95, 85, 80, 75, 60, 45]:
# ❌ 错版:宽条件在前,85 分被"及格"先截走
if score >= 60:
wrong = "B"
elif score >= 80:
wrong = "A" # 永远到不了这里
else:
wrong = "C"
# ✅ 正版:严格条件在前
if score >= 80:
right = "A"
elif score >= 60:
right = "B"
else:
right = "C"
print(score, "分 → 错版:", wrong, " 正版:", right)
95 分 → 错版: B 正版: A 85 分 → 错版: B 正版: A 80 分 → 错版: B 正版: A 75 分 → 错版: B 正版: B 60 分 → 错版: B 正版: B 45 分 → 错版: C 正版: C
1.7 嵌套的分支结构 教材有 · 进度表未点名
多分支能做的事,嵌套分支也能做,但可读性差得多。判断标准很简单:条件是"平级互斥"(A / B / C 选一个)就用 elif;条件有"层次"(先分类、类内再细分)才用嵌套。
is_student = input("是否学生(y/n):") == "y"
distance = float(input("请输入里程(km):"))
if is_student:
# 外层 if 的语句块里,又套了一整套 if-elif-else
if distance <= 6:
price = 2.0
elif distance <= 12:
price = 3.0
else:
price = 4.0
price = price * 0.5 # 学生半价
note = "学生票五折"
else:
if distance <= 6:
price = 2.0
elif distance <= 12:
price = 3.0
else:
price = 4.0
note = "全价"
print("票价:%.1f 元(%s)" % (price, note))
是否学生(y/n):y 请输入里程(km):10 票价:1.5 元(学生票五折)
例:BMI 判断(连续区间用"单边比较链")
weight = float(input("请输入体重(kg):"))
height = float(input("请输入身高(m):"))
bmi = weight / (height ** 2)
print("BMI = %.2f" % bmi)
# 连续区间:只写下界,上界由上一行"继承"
if bmi < 18.5:
print("过轻")
elif bmi < 24:
print("正常")
elif bmi < 28:
print("过重")
else:
print("肥胖")
请输入体重(kg):70 请输入身高(m):1.70 BMI = 24.22 过重
bmi = 23.95 # 明明属于"正常"范围
# PPT 风格:显式写双端区间 —— 会漏掉 23.91~23.99 这一小段
if bmi < 18.5:
r1 = "过轻"
elif 18.5 <= bmi <= 23.9:
r1 = "正常"
elif 24 <= bmi < 28:
r1 = "过重"
else:
r1 = "肥胖" # ← 掉进这里了
# 推荐写法:单边比较 + elif 链,区间无缝衔接
if bmi < 18.5:
r2 = "过轻"
elif bmi < 24:
r2 = "正常"
elif bmi < 28:
r2 = "过重"
else:
r2 = "肥胖"
print("BMI =", bmi)
print("双端区间写法 →", r1)
print("单边比较写法 →", r2)
BMI = 23.95 双端区间写法 → 肥胖 单边比较写法 → 正常
bmi < 18.5 / < 24 / < 28)比写双端区间安全——双端区间会在两个端点之间留下缝,数据掉进缝里就被 else 兜走了。1.8 三元表达式:一句话的二选一 拓展 · 教材未收录
写法:结果 = 值1 if 条件 else 值2。等价于 4 行 if-else,但只适合一行能装下的简单二选一;嵌套三元表达式是著名的"可读性杀手",不提倡。
x = int(input("请输入一个整数:"))
result = "偶数" if x % 2 == 0 else "奇数"
print(result)
请输入一个整数:8 偶数
循环结构
第 2 讲 · 45 分钟 · while · 累加器 · for · range · break / continue · else · 嵌套循环
2.1 什么是循环
教材原话:"循环结构是指在一定条件下重复执行某段代码的流程结构。其中,被重复执行的代码块称为循环体,判断是否继续执行的条件称为终止条件。" Python 只有两种循环语句:while 和 for。
2.2 while 语句 进度表点名
while 三要素(写在黑板上的那种):① 设置循环变量并赋初值 → ② 根据循环变量判断条件 → ③ 在循环体中更新循环变量。教材特别强调:循环体中必须有改变循环条件的语句,否则会进入死循环。
i = 1 # ① 初值
while i <= 5: # ② 条件
print("Hello!", i)
i = i + 1 # ③ 更新(少了这一句就是死循环)
Hello! 1 Hello! 2 Hello! 3 Hello! 4 Hello! 5
| 循环次数 | i | 条件 i <= 5 | 是否进入循环 | 执行 | 更新后 i |
|---|---|---|---|---|---|
| ① | 1 | True | 进入 | 2 | |
| ② | 2 | True | 进入 | 3 | |
| ③ | 3 | True | 进入 | 4 | |
| ④ | 4 | True | 进入 | 5 | |
| ⑤ | 5 | True | 进入 | 6 | |
| 退出 | 6 | False | 不进入 | — | — |
把上面这张表带学生走一遍,比讲十遍语法管用。下面这段是故意写错的死循环反例(别运行,或在 IDLE 里用 Ctrl + C 停):
i = 1
while i <= 5:
print("Hello!", i)
# 忘了写 i = i + 1 → 条件永远成立
NameError。判断该不该用 while 只问一句:"我一开始就知道要跑几遍吗?"2.3 累加器与计数器模式 本章最核心的套路
"求和、求平均、统计个数"这三件事占课后作业的一半,写法都是同一个套路:计数器 i 负责数到几,累加器 total 负责把结果攒起来。
# while 版
total = 0 # 累加器:清零必须放在循环外面
i = 1
while i <= 100:
total += i
i += 1
print("while 版:", total)
# for 版:同样的事,计数交给 Python 管
total = 0
for i in range(1, 101):
total += i
print("for 版:", total)
while 版: 5050 for 版: 5050
# ✅ 正确:清零在循环外
total = 0
i = 1
while i <= 10:
total += i
i += 1
print("正确写法 1~10 累加:", total)
# ❌ 错误:清零写进了循环体,每轮都从头开始
i = 1
while i <= 10:
total = 0 # ← 位置错了
total += i
i += 1
print("错误写法 1~10 累加:", total)
正确写法 1~10 累加: 55 错误写法 1~10 累加: 10
total = 0 必须在循环之前,计数器更新 i = i + 1 必须在循环体里面。错误写法不报错、只是答案不对——所以要养成"用已知答案的小数据自测"的习惯。2.4 for 语句 进度表点名
语法:for 变量 in 序列或迭代对象: 循环体。变量从序列中依次取值,取完就结束——所以 for 循环永远不会死循环。
for name in ['zhang', 'li', 'wang', 'zhao']:
print(name)
zhang li wang zhao
| 场景 | 用哪个 | 理由 |
|---|---|---|
| 次数已知(跑 5 次、遍历列表) | for | 计数交给 Python,不会写错、不会死循环 |
| 次数未知(猜到才停、输入 q 才停) | while | 条件要运行时才知道 |
| 退出条件在循环体中间 | while True + break | for 做不到"中途退出"的通用写法 |
2.5 range() 函数 教材专页
写法 range(start, stop, step):start 起始值(默认 0)、stop 终止值(取不到)、step 步长(默认 1,可为负)。
print("range(5) →", list(range(5)))
print("range(0, 5) →", list(range(0, 5)))
print("range(0, 5, 2) →", list(range(0, 5, 2)))
print("range(10, 0, -2) →", list(range(10, 0, -2)))
print("range(0, -10, -1) →", list(range(0, -10, -1)))
print("range(1, 10) 一共几个数?→", len(range(1, 10)))
range(5) → [0, 1, 2, 3, 4] range(0, 5) → [0, 1, 2, 3, 4] range(0, 5, 2) → [0, 2, 4] range(10, 0, -2) → [10, 8, 6, 4, 2] range(0, -10, -1) → [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] range(1, 10) 一共几个数?→ 9
stop 取不到——range(1, 11) 才是 1 到 10;② range() 返回的是 range 对象,要看得见就套 list();③ 步长不能为 0,否则 ValueError: range() arg 3 must not be zero;④ 递减时 start 必须大于 stop,range(0, 10, -1) 一个数都不产生。range(0, n, 2),比在循环里 if 筛一遍更省事。2.6 break 与 continue 进度表点名
教材原文:continue 跳过本次循环剩余语句、进入下一轮循环;break 跳出循环、进入循环结构后面的语句。多重循环中 break 只能跳出自己所属的那一层;两者通常和 if 配合使用。
# break:i 到 5 就整个结束,后面的 5~9 都不打印
for i in range(10):
if i == 5:
break
print(i, end=",")
print()
# continue:奇数跳过这一轮,偶数照常打印
for i in range(1, 11):
if i % 2 == 1:
continue
print(i, end=",")
print()
0,1,2,3,4, 2,4,6,8,10,
for i in range(5):
for j in range(3):
if j == 2:
break # 只跳出内层 j 循环,外层 i 继续
print(i, j)
0 0 0 1 1 0 1 1 2 0 2 1 3 0 3 1 4 0 4 1
i = i + 1 写在 continue 后面,那一句永远轮不到执行 → 死循环。在 while 里用 continue 之前,先确认递增语句不会被跳过。2.7 循环的 else 子句 进度表点名
教材三条说明:① else 子句是可选的;② 遍历完毕自然结束的会执行 else;③ 执行了 break 提前结束的不会执行 else。
print("—— 有 break:else 不执行 ——")
for i in range(10):
if i == 5:
break
print(i, end=",")
else:
print("Finish!")
print()
print("—— 没有 break:else 执行(continue 不影响 else)——")
for i in range(10):
if i == 5:
continue
print(i, end=",")
else:
print("Finish!")
print()
—— 有 break:else 不执行 —— 0,1,2,3,4, —— 没有 break:else 执行(continue 不影响 else)—— 0,1,2,3,4,6,7,8,9,Finish!
n = int(input("请输入一个大于 1 的整数:"))
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
print(n, "不是素数")
break
else:
print(n, "是素数") # 找了一圈都没找到因子 → 是素数
请输入一个大于 1 的整数:17 17 是素数
2.8 嵌套的循环结构 进度表点名
教材原话:"如果外层循环执行 n 次,内层循环执行 m 次,则整个循环要执行 n × m 次。"图形题(三角、矩形、菱形)全靠嵌套循环。
for i in range(1, 10):
for j in range(1, i + 1): # ← 内层次数随外层变化,这是"三角形"
print(j, "×", i, "=", i * j, end="\t")
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
range(1, i + 1) 依赖外层变量,就能打出三角形;两层都写固定范围就是矩形。综合应用(上机 4 学时)
对应进度表第 7 项:程序控制结构综合应用 · 9 个案例 · 2 次上机
| 编号 | 任务 | 对应知识点 | 难度 | 要求 |
|---|---|---|---|---|
| U1-01 | 判断奇偶数(例 3-1 复现) | 双分支 | ★ | 必做 |
| U1-02 | 成绩等级(例 3-2),测 8 个边界值 | 多分支 | ★★ | 必做 |
| U1-03 | BMI 判断,用单边比较写法 | 多分支 | ★★ | 必做 |
| U1-04 | 分段函数(习题 1) | 多分支 | ★★ | 必做 |
| U1-05 | 三角形判定,重复执行 5 次 | 分支 + while | ★★★ | 必做 |
| U1-06 | 打印 5 次 Hello + 手填执行过程表 | while 三要素 | ★ | 必做 |
| U1-07 | 折纸珠峰(例 3-6) | while | ★★ | 必做 |
| U1-08 | 1+2+…+100(while 版 + for 版) | 累加器 | ★★ | 必做 |
| U1-09 | 故意写一个死循环,用 Ctrl+C 停 | 死循环识别 | ★ | 必做 |
| U2-01~04 | 猜数字四连(if / while / for / while True + break) | 综合 | ★★~★★★ | 必做 |
| U2-05 | 简易登录系统(最多 3 次) | for-else | ★★★ | 必做 |
| U2-06 | 作业 1:1—100 中 5 的倍数累加和 | for + range 步长 | ★★ | 必做 |
| U2-07 | 作业 2:个人所得税(简化三档) | 多分支 | ★★★ | 必做 |
| U2-08/09 | 小明学单词(while 版 / for 版) | while vs for | ★★★ | 选做 |
3.1 三角形判定(while 重复执行 5 次)
根据三边判断能否构成三角形:可以则用海伦公式算面积(保留 2 位小数),否则提示"无法构成三角形",并要求重复执行 5 次——这里正好把"分支 + 循环"合起来用。
import math
i = 1
while i <= 5:
a = float(input("请输入第 1 条边:"))
b = float(input("请输入第 2 条边:"))
c = float(input("请输入第 3 条边:"))
if a + b > c and a + c > b and b + c > a:
p = (a + b + c) / 2
area = math.sqrt(p * (p - a) * (p - b) * (p - c))
print("可以构成三角形,面积为:%.2f" % area)
else:
print("无法构成三角形")
i = i + 1
请输入第 1 条边:3 请输入第 2 条边:4 请输入第 3 条边:5 可以构成三角形,面积为:6.00 请输入第 1 条边:1 请输入第 2 条边:2 请输入第 3 条边:3 无法构成三角形 请输入第 1 条边:6 请输入第 2 条边:8 请输入第 3 条边:10 可以构成三角形,面积为:24.00 请输入第 1 条边:2 请输入第 2 条边:2 请输入第 3 条边:5 无法构成三角形 请输入第 1 条边:5 请输入第 2 条边:5 请输入第 3 条边:5 可以构成三角形,面积为:10.83
a+b>c and a+c>b and b+c>a 最稳;循环负责"做几遍",分支负责"这一遍怎么办"——这是综合题的标准骨架。3.2 折纸珠峰:指数增长的威力
一张 0.1 毫米厚的纸对折多少次能超过珠峰(8848.86 米)?答案会让学生吃惊。
h = 0.0001 # 0.1 毫米换算成米
count = 0
while h < 8848.86: # 次数未知 → 用 while
h = h * 2
count = count + 1
print("需要对折", count, "次")
print("此时厚度为", round(h, 2), "米")
需要对折 27 次 此时厚度为 13421.77 米
3.3 猜数字四连:同一个需求,四种写法
把四份代码并排看,就能体会到"什么时候该用哪个结构"。为了课堂可复现,下面用 seed(1) 固定随机种子(随机数 x = 17);你自己的程序不要写 seed,否则每次答案都一样。
from random import randint, seed
seed(1) # 课堂演示用;自己写程序时删掉这一行
x = randint(0, 100)
guess_number = int(input("输入一个 0~100 之间的整数:"))
if guess_number == x:
print("Bingo!")
elif guess_number > x:
print("Too large!")
else:
print("Too small!")
print("(演示提示:随机数 x =", x, ")")
输入一个 0~100 之间的整数:50 Too large! (演示提示:随机数 x = 17 )
from random import randint, seed
seed(1)
x = randint(0, 100)
i = 0
while i < 5:
guess = int(input("请输入一个 0~100 的整数:"))
if guess == x:
print("Bingo!")
elif guess > x:
print("Too large!")
else:
print("Too small!")
i = i + 1
print("(演示提示:随机数 x =", x, ")")
请输入一个 0~100 的整数:50 Too large! 请输入一个 0~100 的整数:25 Too large! 请输入一个 0~100 的整数:12 Too small! 请输入一个 0~100 的整数:17 Bingo! 请输入一个 0~100 的整数:1 Too small! (演示提示:随机数 x = 17 )
from random import randint, seed
seed(1)
x = randint(0, 100)
for i in range(5): # 与上面的 while 版完全等价
guess = int(input("请输入一个 0~100 的整数:"))
if guess == x:
print("Bingo!")
elif guess > x:
print("Too large!")
else:
print("Too small!")
print("(演示提示:随机数 x =", x, ")")
请输入一个 0~100 的整数:50 Too large! 请输入一个 0~100 的整数:25 Too large! 请输入一个 0~100 的整数:12 Too small! 请输入一个 0~100 的整数:17 Bingo! 请输入一个 0~100 的整数:1 Too small! (演示提示:随机数 x = 17 )
from random import randint, seed
seed(1)
x = randint(0, 100)
while True: # 次数完全未知
s = input("请输入一个 0~100 的整数(输入 q 退出):")
if s == "q":
print("游戏结束,答案是", x)
break
guess = int(s)
if guess == x:
print("Bingo!")
break # 猜中就停
elif guess > x:
print("Too large!")
else:
print("Too small!")
请输入一个 0~100 的整数(输入 q 退出):50 Too large! 请输入一个 0~100 的整数(输入 q 退出):17 Bingo!
3.4 简易登录系统(for-else 的漂亮用法)
要求:用户名密码正确提示"登录成功";最多输入三次;三次都错提示"失败超过三次,请稍后再试!"——"三次都没 break"这件事,正好用 for 的 else 表达。
USERNAME = "admin"
PASSWORD = "123456"
for i in range(3):
u = input("用户名:")
p = input("密码:")
if u == USERNAME and p == PASSWORD:
print("登录成功")
break
else:
left = 2 - i
if left > 0:
print("用户名或密码错误,还剩", left, "次机会")
else:
print("失败超过三次,请稍后再试!")
用户名:admin 密码:wrong 用户名或密码错误,还剩 2 次机会 用户名:admin 密码:123456wrong 用户名或密码错误,还剩 1 次机会 用户名:admin 密码:123456 登录成功
USERNAME = "admin"
PASSWORD = "123456"
for i in range(3):
u = input("用户名:")
p = input("密码:")
if u == USERNAME and p == PASSWORD:
print("登录成功")
break
else:
left = 2 - i
if left > 0:
print("用户名或密码错误,还剩", left, "次机会")
else:
print("失败超过三次,请稍后再试!")
用户名:admin 密码:x 用户名或密码错误,还剩 2 次机会 用户名:admin 密码:y 用户名或密码错误,还剩 1 次机会 用户名:admin 密码:z 失败超过三次,请稍后再试!
3.5 作业 1:1—100 中 5 的倍数的累加和
同一道题三种写法,正好对比"用 if 筛"和"用步长直接生成"的差别。
total = 0
for i in range(1, 101): # 写法 1:全部遍历 + if 筛选
if i % 5 == 0:
total += i
print("写法 1(if 筛选) :", total)
total = 0
for i in range(5, 101, 5): # 写法 2:直接用步长(最优)
total += i
print("写法 2(range 步长) :", total)
print("写法 3(一行搞定) :", sum(range(5, 101, 5)))
写法 1(if 筛选) : 1050 写法 2(range 步长) : 1050 写法 3(一行搞定) : 1050
range(5, 101, 5) 直接生成 5, 10, …, 100,代码更短、跑得更快。三种写法答案必须一致(1050),不一致就说明写错了。3.6 作业 2:个人所得税(简化三档)
税率分三档(单位:万元):x ≤ 3.6 时 x × 3%;3.6 < x ≤ 14.4 时 3.6 × 3% + (x − 3.6) × 10%;x > 14.4 时再加 (x − 14.4) × 20%。输入为负数时提示"输入值错误"。
def tax(x): # 函数第 5 章才正式讲,这里先"会用"
if x < 0:
return "输入值错误"
if x <= 3.6:
y = x * 0.03
elif x <= 14.4:
y = 3.6 * 0.03 + (x - 3.6) * 0.10
else:
y = 3.6 * 0.03 + (14.4 - 3.6) * 0.10 + (x - 14.4) * 0.20
return round(y, 4)
x = float(input("请输入应纳税所得额(万元):"))
print("应缴个税:", tax(x))
请输入应纳税所得额(万元):20 应缴个税: 2.308
3.7 选做题:小明学单词(while 版 vs for 版)
学习 1000 个单词,每天随机学 0—50 个:while 版是"学到 1000 个或满 30 天为止",for 版是"固定 30 天"。这两题是while(条件未知)vs for(次数已知)最好的对照。
from random import randint, seed
seed(7) # 固定种子,方便课堂复现;自己写时删掉
total = 0
day = 0
while total < 1000 and day < 30:
day += 1
n = randint(0, 50)
total += n
print("共用", day, "天,累计学习", total, "个单词")
共用 30 天,累计学习 576 个单词
from random import randint, seed
seed(7)
total = 0
for day in range(1, 31):
n = randint(0, 50)
total += n
print("30 天共学习", total, "个单词")
30 天共学习 576 个单词
seed() 才能复现。AI 渗透(A 类 · 手写 AI 原理)
本章属于基础阶段:学生不使用 AI 工具,只用手写的循环与分支,把 AI 的内部机制跑出来看
AI-1 决策树 = 一串 if / elif / else
决策树的每一个节点就是一次 if 判断,每一条从根到叶的路径就是一串 elif。机器学习里最"可解释"的模型,本质就是嵌套分支。
gpa = float(input("请输入 GPA(0-5):"))
paper = int(input("请输入论文数量:"))
if gpa >= 4.5:
if paper >= 2:
print("决策:直接录取")
else:
print("决策:待定,需面试")
elif gpa >= 3.5:
if paper >= 3:
print("决策:待定,需面试")
else:
print("决策:不录取")
else:
print("决策:不录取")
请输入 GPA(0-5):4.6 请输入论文数量:2 决策:直接录取
AI-2 while = 训练迭代 本章 AI 高潮
AI"训练"的本质:重复同一件事,每次改一点点,直到误差够小。已知 y = 5x,让程序从 w = 1.0 开始自己把 w 学到 5.0。
x = 2.0
y_true = 10.0
w = 1.0 # 循环变量:权重初值(参数初始化)
lr = 0.1 # 学习率
epoch = 0 # 训练轮数计数器
while abs(w * x - y_true) > 0.01: # 终止条件:误差足够小
pred = w * x
w = w - lr * (pred - y_true) * x # 更新:梯度下降
epoch += 1
print("第 %2d 轮:w = %.4f,预测值 = %.4f,误差 = %.4f"
% (epoch, w, w * x, abs(w * x - y_true)))
print("收敛!共训练 %d 轮,最终 w = %.4f(理论答案 5.0)" % (epoch, w))
第 1 轮:w = 2.6000,预测值 = 5.2000,误差 = 4.8000 第 2 轮:w = 3.5600,预测值 = 7.1200,误差 = 2.8800 第 3 轮:w = 4.1360,预测值 = 8.2720,误差 = 1.7280 第 4 轮:w = 4.4816,预测值 = 8.9632,误差 = 1.0368 第 5 轮:w = 4.6890,预测值 = 9.3779,误差 = 0.6221 第 6 轮:w = 4.8134,预测值 = 9.6268,误差 = 0.3732 第 7 轮:w = 4.8880,预测值 = 9.7761,误差 = 0.2239 第 8 轮:w = 4.9328,预测值 = 9.8656,误差 = 0.1344 第 9 轮:w = 4.9597,预测值 = 9.9194,误差 = 0.0806 第 10 轮:w = 4.9758,预测值 = 9.9516,误差 = 0.0484 第 11 轮:w = 4.9855,预测值 = 9.9710,误差 = 0.0290 第 12 轮:w = 4.9913,预测值 = 9.9826,误差 = 0.0174 第 13 轮:w = 4.9948,预测值 = 9.9896,误差 = 0.0104 第 14 轮:w = 4.9969,预测值 = 9.9937,误差 = 0.0063 收敛!共训练 14 轮,最终 w = 4.9969(理论答案 5.0)
lr 从 0.1 改成 0.05,收敛轮数立刻变多;改成 1.5 甚至可能"飞出去"不收敛——这就是调参。改一个数字、看结果怎么变,是最直观的"超参数"体验。AI-3 for = 遍历数据集 + 统计准确率
模型评测的骨架只有三行:for 遍历一条条数据 → 累加器统计对的个数 → 除以总数。
pred = [1, 0, 1, 1, 0, 1, 1, 1, 0, 1] # 模型的预测(1 = 正类)
gold = [1, 0, 1, 0, 0, 1, 1, 1, 0, 1] # 真实标签
correct = 0
for p, g in zip(pred, gold): # 遍历一条条数据 = 一个 epoch
if p == g:
correct += 1 # 累加器
accuracy = correct / len(gold)
print("正确率:%d/%d = %.1f%%" % (correct, len(gold), accuracy * 100))
正确率:9/10 = 90.0%
AI-4 range 的步长 = 分批(batch)
数据太多,一次喂不进模型,所以要分批。分批最朴素的写法就是给 range() 一个步长。
n = 20 # 共 20 条数据
batch_size = 4 # 每批处理 4 条
for start in range(0, n, batch_size): # 步长就是批大小
end = min(start + batch_size, n) # 最后一批可能不满
print("本批处理:第", start, "到", end - 1, "条")
print("一共分了", n // batch_size, "批")
本批处理:第 0 到 3 条 本批处理:第 4 到 7 条 本批处理:第 8 到 11 条 本批处理:第 12 到 15 条 本批处理:第 16 到 19 条 一共分了 5 批
range(0, n, batch_size) 就是最朴素的分批写法。步长越大,每批数据越多、更新次数越少——这就是显存和训练速度的取舍。AI-5 break = 早停(Early Stopping)
验证集准确率连续几轮不涨了就停——再练只会过拟合,还白烧电费。代码上就是一句 break。
x = 2.0
y_true = 10.0
w = 1.0
lr = 0.1
patience = 3 # 容许"连续几轮没有进步"
best_loss = 999
no_improve = 0
for epoch in range(1, 101): # 名义上最多训练 100 轮
pred = w * x
loss = abs(pred - y_true)
if epoch <= 6: # 前 6 轮正常学习
w = w - lr * (pred - y_true) * x
# 第 7 轮起模型"学不动了"(真实训练里常见:学习率衰减到 0、
# 数据重复没有新信息、或已经到精度极限)
if loss < best_loss - 1e-6: # 比历史最好还低 → 有进步
best_loss = loss
no_improve = 0
else: # 没进步
no_improve += 1
print("第 %2d 轮:loss = %.6f,连续 %d 轮没进步"
% (epoch, loss, no_improve))
if no_improve >= patience:
print(">>> 连续 %d 轮没有进步,触发早停!" % patience)
break # 关键:提前跳出循环
print("实际只训练了 %d 轮,而不是 100 轮" % epoch)
第 1 轮:loss = 8.000000,连续 0 轮没进步 第 2 轮:loss = 4.800000,连续 0 轮没进步 第 3 轮:loss = 2.880000,连续 0 轮没进步 第 4 轮:loss = 1.728000,连续 0 轮没进步 第 5 轮:loss = 1.036800,连续 0 轮没进步 第 6 轮:loss = 0.622080,连续 0 轮没进步 第 7 轮:loss = 0.373248,连续 0 轮没进步 第 8 轮:loss = 0.373248,连续 1 轮没进步 第 9 轮:loss = 0.373248,连续 2 轮没进步 第 10 轮:loss = 0.373248,连续 3 轮没进步 >>> 连续 3 轮没有进步,触发早停! 实际只训练了 10 轮,而不是 100 轮
AI-6 continue = 跳过脏数据
真实数据里总有缺失值、异常值。数据清洗的最小单元就是一行 continue:不合格的数据跳过,不计入统计。
scores = [88, 95, -1, 76, None, 92, 103, 85]
total = 0
count = 0
for s in scores:
if s is None or s < 0 or s > 100: # 脏数据判定
continue # 跳过,不计入统计
total += s
count += 1
print("原始数据", len(scores), "条,有效", count, "条")
print("有效成绩平均分:", round(total / count, 2))
原始数据 8 条,有效 5 条 有效成绩平均分: 87.2
dropna()、爬虫的异常兜底,底层逻辑都是"不合格就跳过这一条"。AI-7 循环 else = "这一轮完整跑完了吗"
训练日志里"共 N 轮,全部完成"和"在第 k 轮被中断"是两种完全不同的含义——正是 else 子句要表达的。
print("情况一:正常跑完 3 轮")
for epoch in range(1, 4):
print(" epoch", epoch, ":loss =", round(1.0 / epoch, 4))
else:
print(" === 训练完整完成(未早停)===")
print("情况二:第 2 轮触发早停")
for epoch in range(1, 4):
print(" epoch", epoch, ":loss =", round(1.0 / epoch, 4))
if epoch == 2:
print(" >>> 触发早停!")
break
else:
print(" === 训练完整完成(未早停)===")
情况一:正常跑完 3 轮 epoch 1 :loss = 1.0 epoch 2 :loss = 0.5 epoch 3 :loss = 0.3333 === 训练完整完成(未早停)=== 情况二:第 2 轮触发早停 epoch 1 :loss = 1.0 epoch 2 :loss = 0.5 >>> 触发早停!
AI-8 嵌套循环 = 注意力矩阵的平方代价
一句话 27 个字 → 每个字都要和所有字算一遍相关度 → 27 × 27 = 729 次;字数翻倍到 54 → 54 × 54 = 2916 次(4 倍)。这就是长文本又慢又贵、按 token 收费的根因。
vectors = [
[1, 0, 0, 1], # 词 1
[0, 1, 1, 0], # 词 2
[1, 1, 0, 0], # 词 3
]
names = ["我", "爱", "你"]
print("注意力矩阵(每个词对每个词的相关度):")
print(" " + "".join("%6s" % n for n in names))
count = 0
for i in range(len(vectors)): # 外层:每个词
row = []
for j in range(len(vectors)): # 内层:和每个词算一次
score = 0
for k in range(len(vectors[i])): # 最内层:点积
score += vectors[i][k] * vectors[j][k]
row.append(score)
count += 1
print(" %2s " % names[i] + "".join("%6d" % v for v in row))
print("总共执行了 %d 次点积 = 3 x 3" % count)
print("三层嵌套一共 %d 次乘法 = 3 x 3 x 4" % (3 * 3 * 4))
注意力矩阵(每个词对每个词的相关度):
我 爱 你
我 2 0 1
爱 0 2 1
你 1 1 2
总共执行了 9 次点积 = 3 x 3
三层嵌套一共 36 次乘法 = 3 x 3 x 4
交互演示区
改参数、点按钮,立刻看结果 · 浏览器内 JS 模拟(已与 Python 运行结果逐一对齐)
演示 1:成绩等级判定(多分支)
演示 2:BMI 判定(单边比较链)
演示 3:素数判断(for-else)
演示 4:九九乘法表(嵌套循环)
演示 5:while 训练迭代(AI-2)
知识点总结卡 · 易错清单 · 作业
Summary · 学生速查 / 可打印
选择结构总结卡
| 结构 | 写法 | 何时用 | 一句话 |
|---|---|---|---|
| 单分支 | if 条件: | 满足才做,不满足就算了 | 一条路,可选 |
| 双分支 | if 条件: … else: … | 二选一 | else 后面不写条件 |
| 多分支 | if / elif / else | 三选一以上 | 条件从严到宽,命中即停 |
| 嵌套 | if 里套 if | 判断有先后依赖 | 能 elif 就别嵌套 |
| 三元 | a if 条件 else b | 简单二选一赋值 | 只装得下一行才用 |
循环结构总结卡
| 结构 | 写法 | 何时用 | 一句话 |
|---|---|---|---|
| while | while 条件: | 次数未知 | 初值 / 条件 / 更新,缺一不可 |
| for | for i in 序列: | 次数已知、遍历 | 计数交给 Python,不会死循环 |
| range | range(start, stop, step) | 生成整数序列 | 含头不含尾,步长默认 1 |
| break | 满足条件即跳出 | 提前结束 | 多重循环只跳一层 |
| continue | 跳过本轮剩余 | 跳过个别数据 | while 里别跳过递增语句 |
| else | 挂在循环后 | "跑完了吗" | 有 break 就没 else |
| 嵌套 | 循环里套循环 | 图形、矩阵 | 总次数 = n × m |
本章语法 ↔ AI 概念对照卡
| 本章语法 | AI 里的对应物 | 本页演示 |
|---|---|---|
| if / elif / else | 决策树的节点与路径 | AI-1 |
| 嵌套 if | 随机森林里的多棵树 | AI-1 |
| while | 训练迭代(epoch 循环) | AI-2 |
| for | 遍历数据集(一个 epoch) | AI-3 |
| 累加器 + 除法 | 准确率 accuracy | AI-3 |
| range 的步长 | 批大小 batch size | AI-4 |
| break | Early Stopping 早停 | AI-5 |
| continue | 跳过脏数据(dropna) | AI-6 |
| 循环 else | "训练是否完整跑完" | AI-7 |
| 嵌套循环 | 注意力矩阵(计算量平方增长) | AI-8 |
| 猜数字的"猜—反馈—修正" | RLHF 人类反馈强化学习 | §3.3 |
易错清单(巡机速查)
| 编号 | 症状 | 原因 | 一句话诊断 |
|---|---|---|---|
| E-01 | IndentationError | 缩进不一致 | 空格和 Tab 混用了 |
| E-02 | 逻辑悄悄错了,不报错 | 缩进层级写偏 | 看 print 应该在第几层 |
| E-03 | SyntaxError 指向 else | else 后面写了条件 | else 不跟条件 |
| E-04 | 高分被判低等级 | elif 顺序从宽到严 | 严格条件要放前面 |
| E-05 | 程序停不下来 | 循环体里没更新循环变量 | 补上 i = i + 1 |
| E-06 | 累加结果只剩最后一个数 | total = 0 写进了循环体 | 清零要放在循环外 |
| E-07 | range 少跑一次 | 忘了 stop 取不到 | 数到 n 就写 n+1 |
| E-08 | ValueError: range() arg 3 must not be zero | 步长写了 0 | 步长不能为 0 |
| E-09 | break 只跳出一层 | 只作用于所属层 | 需要多层退出用标志变量 |
| E-10 | continue 后死循环 | while 里跳过了递增语句 | 递增写在 continue 之前 |
| E-11 | else 子句不执行 | 循环里执行了 break | 有 break 就没 else |
| E-12 | 还没分等级就报错 | 把 = 写成 ==,或括号没配平 | 看报错指的那一行 |
课后作业
必做(下次课课前检查)
- 求 1—100 之间 5 的倍数的累加和(答案应为 1050,用两种写法各做一遍)。
- 求个人所得税(简化三档,单位万元),至少测试 3.6、14.4 两个边界值。
加分题
- 把本章任意一个例子改成
while True + break的写法,并说明改了什么、为什么。
课堂自测(8 题 · 5 分钟)
range(1, 10)产生几个数?list(range(0, 5, 2))的结果是什么?- while 的三要素是什么?
break和continue的区别是什么?- 循环的
else什么时候执行? for i in range(3): for j in range(4)一共循环几次?- 成绩 85 分,按"先判断 ≥60 再判断 ≥80"的顺序写,结果是什么?
- 1—100 的奇数和是多少?
查看 8 题答案
- 9 个(1 到 9,stop 取不到)
[0, 2, 4]- 初值、条件、更新
- break 结束整个循环;continue 只跳过本轮
- 循环自然结束(没有遇到 break)时执行
- 12 次(3 × 4)
- B(错的,应该是 A —— 宽条件在前,85 ≥ 60 先命中)
- 2500
备课提醒:本讲课件(PPT)需要注意的几处
- S01 目录页写的是第 2 章内容,讲课时跳过,用 S03/S04 开场。
- S04 与 S16 有两套章节编号(S04:3.1 顺序 / 3.2 选择 / 3.3 循环;S16:3.1 选择 / 3.2 循环),以 S04 为准。
- S10 的三角形题要求"重复执行 5 次",但循环到 S17 才讲——建议移到上机一,本页已按此安排。
- 例题编号有重复:S10 与 S37 都叫例 3-3,S24 与 S28 都叫例 3-7,讲课时说清楚"两个 3-3"。S23 是空页,直接跳过。
- S29 / S34 的代码里混入了中文全角引号(如
print(i,end=’,’)),复制到 IDLE 会直接SyntaxError——让学生手打,不要复制。 - S42 的个税题:"调佣函数"应为"调用函数";单位是万元但没标注;且真实个税是七级超额累进,这里是简化三档,不要拿去真算工资。