03-流程控制
本章代码基于 Python 3.11+ 编写
流程控制让程序能够根据条件做出决策和重复执行任务。本章重点介绍 Python 3.10+ 新增的 match 语句。
本章讲解条件语句和循环语句,控制程序的执行流程。
概念铺垫
为什么需要流程控制?一个真实的决策场景
问题场景: 你在开发一个自动售货机程序,需要根据用户投入金额决定可以购买的饮料。
不使用流程控制的困惑:
- 程序只能"直线执行",无法根据条件做出不同反应
- 如何处理"如果...就...否则..."的情况?
使用流程控制的解决方案:
# 自动售货机决策
money: float = 4.0
# 使用 if 判断
if money >= 5:
print("可以购买可乐(5元)")
elif money >= 4:
print("可以购买雪碧(4元)")
elif money >= 3:
print("可以购买矿泉水(3元)")
else:
print("金额不足,请继续投币")这就是流程控制的价值:让程序能够根据条件做出决策。
流程控制解决了什么问题?
流程控制的本质是:让程序能够根据条件做出不同的反应。
就像你早上出门前的决策:
- 如果下雨 → 带伞
- 如果不下雨 → 不带伞
流程控制的类型:
- 分支(if):根据条件选择不同路径
- 循环(for/while):重复执行相同操作
- 模式匹配(match):根据数据结构选择处理方式(Python 3.10+)
流程控制的最简用法(5分钟上手)
最简单的 if 语句:
age: int = 18
if age >= 18:
print("成年人")
else:
print("未成年人")最简单的 for 循环:
# 打印1到5
for i in range(1, 6):
print(i)最简单的 match 语句(Python 3.10+):
status: str = "success"
match status:
case "success":
print("操作成功")
case "error":
print("操作失败")
case _:
print("未知状态")这就是流程控制的基本用法。接下来我们详细学习更多功能。
什么是流程控制
流程控制(Control Flow) 决定程序中代码的执行顺序。
┌─────────────────────────────────────────┐
│ Python 流程控制类型 │
├─────────────────────────────────────────┤
│ │
│ 1. 条件分支(选择结构) │
│ • if 语句 │
│ • if-else 语句 │
│ • if-elif-else 语句 │
│ │
│ 2. 循环结构(重复执行) │
│ • for 循环 │
│ • while 循环 │
│ │
│ 3. 循环控制 │
│ • break(跳出循环) │
│ • continue(跳过本次) │
│ │
└─────────────────────────────────────────┘L1 理解层:会用
if 语句
基本语法
┌─────────────────────────────────────────┐
│ if 语句语法 │
├─────────────────────────────────────────┤
│ │
│ if 条件: │
│ 条件成立时执行的代码 │
│ │
│ 语法要点: │
│ • 条件 → 返回 True 或 False │
│ • : → 冒号,不能省略! │
│ • 缩进 → 代码块必须缩进(4 空格) │
│ │
└─────────────────────────────────────────┘示例
# 判断是否为正数
number = 5
if number > 0:
print(f"{number} 是正数")if-else 语句
基本语法
┌─────────────────────────────────────────┐
│ if-else 语句语法 │
├─────────────────────────────────────────┤
│ │
│ if 条件: │
│ 条件成立时执行 │
│ else: │
│ 条件不成立时执行 │
│ │
└─────────────────────────────────────────┘示例
# 判断奇偶数
number = 7
if number % 2 == 0:
print(f"{number} 是偶数")
else:
print(f"{number} 是奇数")if-elif-else 语句
基本语法
┌─────────────────────────────────────────┐
│ if-elif-else 语句语法 │
├─────────────────────────────────────────┤
│ │
│ if 条件 1: │
│ 条件 1 成立时执行 │
│ elif 条件 2: │
│ 条件 2 成立时执行 │
│ else: │
│ 以上都不成立时执行 │
│ │
│ 注意:一旦某个条件满足,立即退出 │
│ │
└─────────────────────────────────────────┘示例:成绩等级
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"
print(f"等级:{grade}") # 输出:等级:Bpass 语句(占位符)
什么是 pass
pass 是一个占位语句,不执行任何操作。当语法上需要语句但不需要执行任何代码时使用。
┌─────────────────────────────────────────┐
│ pass 的作用 │
├─────────────────────────────────────────┤
│ │
│ • 占位:先占个位置,以后再写代码 │
│ • 空块:保持语法完整性 │
│ • 跳过:暂时跳过某些条件 │
│ │
│ pass 执行时:什么都不做,直接跳过 │
│ │
└─────────────────────────────────────────┘为什么需要 pass
问题:Python 不允许空代码块
# ❌ 错误:if 后面不能是空的
if True:
# 语法错误!Expected an indented block
# ✅ 正确:使用 pass 占位
if True:
pass # 暂时什么都不做实际场景
场景1:函数占位(开发中)
def calculate_tax(income: float) -> float:
"""计算税费(待实现)"""
pass # TODO: 稍后实现
def process_data(data: list) -> None:
"""处理数据(待实现)"""
pass # TODO: 稍后实现
# 代码可以正常运行
calculate_tax(5000) # 不会报错
process_data([1, 2, 3])场景2:类定义占位
class User:
"""用户类(待实现)"""
pass # 先定义类名,稍后添加属性和方法
class Database:
pass
# 可以创建实例
user = User()
db = Database()场景3:条件分支占位
status: str = "pending"
if status == "approved":
process_order()
elif status == "rejected":
notify_user()
elif status == "pending":
pass # 待处理状态,暂不操作
else:
handle_unknown()场景4:循环体占位
# 读取文件直到结束
with open("data.txt") as f:
for line in f:
pass # 只读取,不处理
# 或者等待某个条件
while not ready:
pass # 等待中...pass vs ... (Ellipsis)
Python 中还有一个类似的占位符:...(三个点,Ellipsis)
# pass 和 ... 都可以用于占位
def func1():
pass # 传统写法
def func2():
... # Python 3 新写法(更简洁)
# 两者效果相同
# ... 在类型提示和切片中也有其他用途推荐: 在代码占位时使用 pass 更明确。
常见用法总结
┌─────────────────────────────────────────────────────┐
│ pass 常见用法 │
├─────────────────────────────────────────────────────┤
│ │
│ 1. 函数/方法占位(待实现) │
│ def my_func(): │
│ pass │
│ │
│ 2. 类定义占位 │
│ class MyClass: │
│ pass │
│ │
│ 3. 条件分支占位 │
│ if condition: │
│ pass # 暂不处理 │
│ │
│ 4. 异常处理占位 │
│ try: │
│ risky_operation() │
│ except ValueError: │
│ pass # 忽略这个错误 │
│ │
└─────────────────────────────────────────────────────┘三元运算符
概念说明
三元运算符(又称三元表达式)是简化版的 if-else 语句,用于在一行内根据条件选择值。它是 Python 中唯一的条件表达式。
为什么需要三元运算符?
- 简化代码:将简单的 if-else 压缩成一行
- 提高可读性:适合赋值场景,一眼就能看清"条件→结果"
- 函数式风格:常用于 Lambda、列表推导等场景
语法特点:
- Python 的三元运算符语法与 C/Java 不同
- 顺序是"结果在前,条件在后":
值1 if 条件 else 值2 - 不支持
elif,只处理两种情况
基本语法
┌─────────────────────────────────────────────────────┐
│ 三元运算符语法 │
├─────────────────────────────────────────────────────┤
│ │
│ 值 1 if 条件 else 值 2 │
│ │
│ 执行逻辑: │
│ • 条件为 True → 返回值 1 │
│ • 条件为 False → 返回值 2 │
│ │
│ 与其他语言对比: │
│ C/Java: 条件 ? 值1 : 值2 │
│ Python: 值1 if 条件 else 值2 │
│ │
│ Python 的写法更像自然语言: │
│ "成年 如果 年龄>=18 否则 未成年" │
│ │
└─────────────────────────────────────────────────────┘示例代码
示例 1:基础用法
# 传统 if-else 写法(4行)
age = 20
if age >= 18:
status = "成年"
else:
status = "未成年"
# 三元运算符写法(1行)
status = "成年" if age >= 18 else "未成年"
print(status) # 成年示例 2:用于变量赋值
# 根据分数判断是否通过
score: int = 75
result: str = "通过" if score >= 60 else "不及格"
print(result) # 通过
# 根据温度选择衣物
temperature: int = 28
clothing: str = "短袖" if temperature > 25 else "外套"
print(clothing) # 短袖示例 3:用于函数返回值
def get_sign(number: int) -> str:
"""返回数字的正负符号"""
return "正数" if number > 0 else "负数或零"
print(get_sign(10)) # 正数
print(get_sign(-5)) # 负数或零
def get_max(a: int, b: int) -> int:
"""返回较大值(简化版)"""
return a if a > b else b
print(get_max(3, 5)) # 5示例 4:用于列表推导
# 筛选并标记
scores: list[int] = [85, 45, 92, 58, 78]
labels: list[str] = ["通过" if s >= 60 else "不及格" for s in scores]
print(labels) # ['通过', '不及格', '通过', '不及格', '通过']
# 将负数转为 0
numbers: list[int] = [-3, 5, -1, 8, 0]
normalized: list[int] = [n if n > 0 else 0 for n in numbers]
print(normalized) # [0, 5, 0, 8, 0]关键代码解释:
| 代码片段 | 读法 | 含义 |
|---|---|---|
"通过" if s >= 60 else "不及格" | 如果分数 ≥ 60 取"通过",否则取"不及格" | 三元表达式作为列表元素 |
[... for s in scores] | 对 scores 每个元素 s 执行前面的表达式 | 列表推导式遍历 scores |
[n if n > 0 else 0 for n in numbers] | 正数保留,负数/零替换为 0 | 三元 + 推导组合:先判断再收集 |
示例 5:用于 Lambda 函数
# Lambda 中不能使用 if-else 语句,但可以用三元表达式
is_even: callable = lambda x: "偶数" if x % 2 == 0 else "奇数"
print(is_even(4)) # 偶数
print(is_even(7)) # 奇数
# 条件计算
safe_divide: callable = lambda a, b: a / b if b != 0 else 0
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # 0关键代码解释:
| 代码片段 | 含义 | 为什么用三元而不是 if-else |
|---|---|---|
lambda x: "偶数" if x % 2 == 0 else "奇数" | 匿名函数:输入 x,返回奇偶判断字符串 | lambda 体只能是表达式,不能用语句(if-else 是语句) |
lambda a, b: a / b if b != 0 else 0 | 安全除法:除数不为 0 才做除法,否则返回 0 | 三元表达式防止除零错误(ZeroDivisionError) |
嵌套三元运算符
三元运算符可以嵌套,但要注意可读性:
# 嵌套三元运算符(多层条件)
score: int = 85
grade: str = "A" if score >= 90 else ("B" if score >= 80 else "C")
print(grade) # B
# 嵌套过多时,建议改用 if-elif-else
# 更清晰的写法:
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"可读性建议:
- 单层三元运算符:推荐使用,简洁清晰
- 双层嵌套:可接受,但需要括号明确优先级
- 三层及以上:不推荐,改用 if-elif-else
三元运算符 vs if-else 对比
┌─────────────────────────────────────────────────────┐
│ 三元运算符 vs if-else 对比 │
├─────────────────────────────────────────────────────┤
│ │
│ 特性 三元运算符 if-else │
│ ────── ──────── ────── │
│ 适用场景 简单二选一赋值 复杂多分支逻辑 │
│ 代码行数 1 行 4+ 行 │
│ 可读性 简单时更高 复杂时更高 │
│ 功能 只支持两种结果 支持多分支 │
│ 嵌套能力 可嵌套但不推荐 自由嵌套 │
│ │
│ 选择建议: │
│ • 简单赋值 → 三元运算符 │
│ • 多分支判断 → if-elif-else │
│ • 函数式场景(Lambda/推导)→ 三元运算符 │
│ │
└─────────────────────────────────────────────────────┘match 语句(Python 3.10+)
概念说明
match 语句 是 Python 3.10 引入的新特性,用于模式匹配,可以替代复杂的 if-elif 链。
┌─────────────────────────────────────────┐
│ match 语句优势 │
├─────────────────────────────────────────┤
│ │
│ ✅ 代码更清晰易读 │
│ ✅ 支持结构模式匹配 │
│ ✅ 支持解构复杂数据 │
│ ✅ 减少条件判断嵌套 │
│ │
└─────────────────────────────────────────┘基础用法
# 替代复杂的 if-elif chain
status: str = "success"
match status:
case "success":
print("操作成功")
case "error":
print("操作失败")
case "pending":
print("处理中")
case _:
print("未知状态") # _ 是通配符高级用法:结构模式匹配
# 匹配数据结构
def process_command(cmd: dict[str, str]) -> str:
match cmd:
case {"action": "move", "direction": d}:
return f"向{d}移动"
case {"action": "attack", "target": t}:
return f"攻击{t}"
case {"action": "quit"}:
return "退出游戏"
case _:
return "未知命令"
# 使用示例
cmd1: dict[str, str] = {"action": "move", "direction": "北"}
print(process_command(cmd1)) # 输出:向北移动
cmd2: dict[str, str] = {"action": "attack", "target": "怪物"}
print(process_command(cmd2)) # 输出:攻击怪物关键代码解释:
| 代码 | 含义 | 说明 |
|---|---|---|
case {"action": "move", "direction": d}: | 匹配包含 action="move" 的字典,并将 direction 的值绑定到变量 d | d 是捕获变量,自动提取字典中对应键的值 |
case {"action": "attack", "target": t}: | 匹配 action="attack",并捕获 target 的值到 t | 不同 case 可以捕获不同的字段 |
case _: | 通配符,匹配所有未被前面 case 匹配的情况 | 相当于 else,避免遗漏处理 |
匹配多个值
# 使用 | 匹配多个值
grade: str = "B"
match grade:
case "A" | "A+":
print("优秀")
case "B" | "B+":
print("良好")
case "C":
print("中等")
case _:
print("需要努力")匹配序列
# 匹配列表结构
def analyze_list(items: list[int]) -> str:
match items:
case []:
return "空列表"
case [x]:
return f"单元素:{x}"
case [x, y]:
return f"两个元素:{x} 和 {y}"
case [first, *rest]:
return f"首元素:{first}, 其余:{rest}"
case _:
return "未知结构"
print(analyze_list([])) # 空列表
print(analyze_list([1])) # 单元素:1
print(analyze_list([1, 2, 3, 4])) # 首元素:1, 其余:[2, 3, 4]match vs if-elif 对比
┌─────────────────────────────────────────────────────┐
│ match vs if-elif 对比 │
├─────────────────────────────────────────────────────┤
│ │
│ 特性 if-elif match │
│ ────── ─────── ───── │
│ 适用场景 简单条件判断 模式匹配、结构解构 │
│ 代码可读性 条件多时较复杂 结构清晰 │
│ 功能 基础判断 高级模式匹配 │
│ 版本要求 所有版本 Python 3.10+ │
│ │
│ 选择建议: │
│ • 简单条件判断 → if-elif │
│ • 多分支等值判断 → match │
│ • 解构数据结构 → match │
│ │
└─────────────────────────────────────────────────────┘for 循环
基本语法
┌─────────────────────────────────────────┐
│ for 循环语法 │
├─────────────────────────────────────────┤
│ │
│ for 变量 in 可迭代对象: │
│ 循环体 │
│ │
└─────────────────────────────────────────┘示例:遍历列表
fruits = ["apple", "banana", "orange"]
for fruit in fruits:
print(fruit)
# 输出:
# apple
# banana
# orangerange() 函数
三种用法
┌─────────────────────────────────────────┐
│ range() 函数用法 │
├─────────────────────────────────────────┤
│ │
│ 1. range(stop) │
│ 生成 0 到 stop-1 的数字 │
│ range(5) → 0, 1, 2, 3, 4 │
│ │
│ 2. range(start, stop) │
│ 生成 start 到 stop-1 的数字 │
│ range(2, 5) → 2, 3, 4 │
│ │
│ 3. range(start, stop, step) │
│ 生成 start 到 stop-1,步长为 step │
│ range(0, 10, 2) → 0, 2, 4, 6, 8 │
│ │
│ 注意:包头不包尾 │
│ │
└─────────────────────────────────────────┘示例
# 计算 1+2+3+...+100
total = 0
for i in range(1, 101):
total += i
print(total) # 5050
# 倒序循环
for i in range(5, 0, -1):
print(i, end=" ")
# 输出:5 4 3 2 1while 循环
基本语法
┌─────────────────────────────────────────┐
│ while 循环语法 │
├─────────────────────────────────────────┤
│ │
│ while 条件: │
│ 循环体 │
│ │
│ ⚠️ 必须在循环体内修改条件, │
│ 否则会变成无限循环! │
│ │
└─────────────────────────────────────────┘示例
# 从 1 数到 5
count = 1
while count <= 5:
print(count)
count += 1
# 输出:1 2 3 4 5break 和 continue
break:跳出整个循环
# 找到第一个能被 7 整除的数
for i in range(1, 101):
if i % 7 == 0:
print(f"找到了:{i}")
break
# 输出:找到了:7continue:跳过本次循环
# 只打印奇数
for i in range(1, 11):
if i % 2 == 0:
continue
print(i, end=" ")
# 输出:1 3 5 7 9对比
break: continue:
i=1 → 打印 1 i=1 → 打印 1
i=2 → 打印 2 i=2 → 打印 2
i=3 → break → 退出 i=3 → continue → 跳过
⨯ 不打印 i=4 → 打印 4
⨯ 不打印 i=5 → 打印 5循环的 else 子句
# 循环正常结束时执行 else
for i in range(5):
print(i)
else:
print("循环正常结束")
# 被 break 打断时不执行 else
for i in range(5):
if i == 3:
break
print(i)
else:
print("这句话不会打印")从简单到复杂:流程控制的渐进应用
层级1:单一条件
# 简单 if
age: int = 18
if age >= 18:
print("成年人")层级2:多条件判断
# if-elif-else
score: int = 85
if score >= 90:
print("优秀")
elif score >= 60:
print("通过")
else:
print("不及格")层级3:嵌套条件
# 嵌套 if
score: int = 85
has_bonus: bool = True
if score >= 60:
if has_bonus:
print("通过 + 加分")
else:
print("通过")层级4:循环 + 条件
# 筛选数据
scores: list[int] = [85, 45, 92, 58, 78]
passed: list[int] = []
for score in scores:
if score >= 60:
passed.append(score)
print(f"通过人数:{len(passed)}")层级5:match 语句应用
# 使用 match 简化判断(Python 3.10+)
action: str = "attack"
match action:
case "move":
print("移动")
case "attack":
print("攻击")
case "defend":
print("防御")
case _:
print("未知动作")综合应用:自动售货机模拟程序
这个示例综合运用流程控制的多个知识点:
# 自动售货机程序(Python 3.11+)
def vending_machine() -> None:
"""自动售货机模拟程序"""
print("=== 自动售货机 ===")
print("可乐:5元 | 矿泉水:3元 | 雪碧:4元")
balance: float = 0.0
while True:
print(f"当前余额:{balance:.1f}元")
action: str = input("投币(I) / 购买(B) / 退出(Q): ").upper()
match action:
case "I":
coin: float = float(input("投入金额:"))
balance += coin
case "B":
drink: str = input("选择饮料(可乐/矿泉水/雪碧):")
price_map: dict[str, float] = {
"可乐": 5.0,
"矿泉水": 3.0,
"雪碧": 4.0
}
price: float = price_map.get(drink, 0.0)
if balance >= price:
balance -= price
print(f"购买成功!剩余余额:{balance:.1f}元")
else:
print(f"余额不足!{drink}需要{price}元")
case "Q":
print(f"退出,返还余额:{balance:.1f}元")
break
case _:
print("无效操作")
if __name__ == "__main__":
vending_machine()这个程序展示了:
- if/elif 判断条件
- while 循环持续运行
- match 语句处理用户输入
- 循环与条件的组合使用
- break 跳出循环
关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
while True: | 无限循环,程序持续等待用户操作 | 售货机需要反复响应,直到用户主动退出 |
input(...).upper() | 获取输入后立即转大写 | 让用户输入 i/I 都能识别,不区分大小写 |
match action: case "I": | 根据用户操作分支执行 | match 比 if-elif 链更清晰,每个 case 独立 |
price_map.get(drink, 0.0) | 安全查找饮料价格,不存在则返回 0.0 | 避免用户输入不存在的饮料时 KeyError 崩溃 |
balance += coin | 复合赋值:余额加上投入金额 | += 等价于 balance = balance + coin,更简洁 |
break | 退出 while 循环 | Q 操作后用 break 结束无限循环 |
L2 实践层:用好
推荐做法
| 做法 | 原因 | 示例 |
|---|---|---|
| 用守卫子句(guard clause)替代深层嵌套 | 减少缩进层级,提高可读性 | if not valid: return |
| 简单二选一赋值用三元运算符 | 一行表达,语义清晰 | "通过" if s >= 60 else "不及格" |
| 多分支等值判断用 match(3.10+) | 语法清晰,支持结构化匹配 | match status: case "ok": ... |
遍历序列用 for,条件循环用 while | 各司其职,语义准确 | for item in items: |
for 优先于 while(有明确范围时) | 避免忘更条件导致死循环 | for i in range(10): |
提前 break 替代 while 条件累积 | 减少循环条件复杂度 | while True: if done: break |
for-else 用于"搜索未找到"模式 | 比标志变量更优雅 | for x in items: if found: break else: not_found() |
用 pass 而非空注释占位 | 语义明确,编辑器友好 | def todo(): pass |
反模式:不要这样做
# ❌ 错误:深层嵌套(箭头代码)
def process(data):
if data:
if data.valid:
if data.has_permission:
if data.is_ready:
return do_work(data)
return None
# ✅ 正确:用守卫子句扁平化
def process(data):
if not data:
return None
if not data.valid:
return None
if not data.has_permission:
return None
if not data.is_ready:
return None
return do_work(data)# ❌ 错误:简单等值判断使用 if-elif 链
if status == "success":
...
elif status == "error":
...
elif status == "pending":
...
else:
...
# ✅ 正确:用 match(Python 3.10+)
match status:
case "success":
...
case "error":
...
case "pending":
...
case _:
...# ❌ 错误:滥用三元运算符导致难读
result = "A" if s > 90 else "B" if s > 80 else "C" if s > 70 else "D" if s > 60 else "F"
# ✅ 正确:多档判断用 if-elif-else
if s > 90:
result = "A"
elif s > 80:
result = "B"
elif s > 70:
result = "C"
elif s > 60:
result = "D"
else:
result = "F"# ❌ 错误:for 循环中使用 else 后没有注释说明意图
for item in items:
if item == target:
break
else:
print("未找到") # 新手看不懂 else 的含义
# ✅ 正确:在 else 附近加注释,或改用标志变量
found = False
for item in items:
if item == target:
found = True
break
if not found:
print("未找到")# ❌ 错误:用 while 遍历已知序列
i = 0
while i < len(items):
print(items[i])
i += 1
# ✅ 正确:用 for 循环
for item in items:
print(item)常见陷阱
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| 缩进混用 Tab 和空格 | Python 3 不允许混用 | 统一使用 4 空格缩进 |
= vs == 混淆 | 赋值和比较符号不同 | 条件中只写 ==,不写 = |
while 忘更条件 | 导致无限循环 | while True + break 模式,或确保条件会被修改 |
for-else 语义误解 | else 在 break 后不执行,新手易困惑 | 谨慎使用,必要时加注释 |
match 通配符 _ vs 变量 | case _: 是通配符,case x: 会绑定变量 | 注意 _ 的特殊语义 |
| 列表遍历中修改列表 | for item in items: items.remove(item) 会跳过元素 | 遍历副本 for item in items[:]: |
| 可变对象作循环变量 | 在循环中修改列表元素影响后续迭代 | 使用不可变对象或确保不会副作用 |
适用场景
| 场景 | 是否推荐 | 原因 |
|---|---|---|
| 简单条件判断 | ✅ if-else | 最基础的流程控制 |
| 多条件、互斥分支 | ✅ if-elif-else | 语义清晰,顺序判断 |
| 多分支等值匹配 | ✅ match(3.10+) | 模式匹配更强大 |
| 已知次数/范围循环 | ✅ for + range() | 安全,不会死循环 |
| 条件未知、动态循环 | ✅ while | 灵活,适合不确定迭代次数 |
| 函数参数校验 | ✅ 守卫子句 + 提前返回 | 减少嵌套 |
| 简单二选一赋值 | ✅ 三元运算符 | 一行解决 |
| 多层条件赋值 | ❌ 嵌套三元 | 改用 if-elif-else |
| 搜索特定元素 | ✅ for + break + else | Python 特有的优雅模式 |
| 大循环中判断 | ✅ 提前 continue 跳过 | 减少缩进,提高可读性 |
| 不关心循环变量 | ✅ for _ in range(n): | _ 约定表示"忽略" |
L3 专家层:深入
Python 如何实现流程控制
match/case 语句的编译机制
match 语句并非简单的 if-elif 链翻译,它有专门的字节码优化。
match 语句内部实现:
┌─────────────────────────────────────────────────────────────┐
│ match/case 的编译策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 编译时,match 会根据模式复杂度选择策略: │
│ │
│ 简单字面量匹配(如 case 1, case 2, ...): │
│ → 编译为跳转表(类似 switch),O(1) 分发 │
│ │
│ 序列模式匹配(如 case [x, y]): │
│ → 编译为 MATCH_SEQUENCE + 长度检查 + 绑定 │
│ │
│ 映射模式匹配(如 case {"key": v}): │
│ → 编译为 MATCH_MAPPING + 键检查 + 绑定 │
│ │
│ 类模式匹配(如 case Point(x=0, y=0)): │
│ → 编译为 MATCH_CLASS + isinstance + 属性匹配 │
│ │
│ 关键字节码指令(Python 3.10+ 新增): │
│ • MATCH_SEQUENCE - 序列匹配 │
│ • MATCH_MAPPING - 映射匹配 │
│ • MATCH_CLASS - 类匹配 │
│ • MATCH_KEYS - 键提取 │
│ │
└─────────────────────────────────────────────────────────────┘验证代码:
from dis import dis
# 简单字面量 match(编译为跳转表)
code = """
match x:
case 1: print("一")
case 2: print("二")
case 3: print("三")
case _: print("未知")
"""
dis(code)
# 可以看到 COPY, LOAD_CONST, COMPARE_OP 等指令序列
# 每个 case 是线性的比较链(简单情况)
# 序列模式 match
code = """
match items:
case [x]: print(x)
case [x, y]: print(x, y)
case [first, *rest]: print(first, rest)
"""
dis(code)
# 可以看到 MATCH_SEQUENCE 指令
# 字典模式 match
code = """
match cmd:
case {"action": a}: print(a)
case {"action": a, "target": t}: print(a, t)
"""
dis(code)
# 可以看到 MATCH_MAPPING, MATCH_KEYS 指令循环的字节码与性能
for vs while 的字节码差异:
┌─────────────────────────────────────────────────────────────┐
│ for/while 的底层执行路径 │
├─────────────────────────────────────────────────────────────┤
│ │
│ for i in range(n): │
│ ────────────── │
│ 1. GET_ITER 获取 range 迭代器 │
│ 2. FOR_ITER 调用 __next__,StopIteration 时跳转 │
│ 3. STORE_NAME 绑定到循环变量 │
│ 4. 执行循环体 │
│ 5. JUMP_ABSOLUTE 回到 FOR_ITER │
│ │
│ while i < n: │
│ ────────── │
│ 1. LOAD_NAME 加载 i │
│ 2. LOAD_NAME 加载 n │
│ 3. COMPARE_OP 执行 < 比较 │
│ 4. POP_JUMP_IF_FALSE 条件不成立时跳出 │
│ 5. 执行循环体 │
│ 6. JUMP_ABSOLUTE 回到第1步 │
│ │
│ 结论:for 使用 C 实现的迭代器,while 每条都用 Python 比较 │
│ → for 通常比 while 快,尤其是大循环 │
│ │
└─────────────────────────────────────────────────────────────┘验证代码:
from dis import dis
dis("for i in range(10): print(i)")
# 关键指令:GET_ITER, FOR_ITER
dis("i = 0\nwhile i < 10: print(i); i += 1")
# 关键指令:COMPARE_OP, POP_JUMP_IF_FALSE
# 每次循环都执行 LOAD_NAME + COMPARE_OP
import timeit
n = 1_000_000
# for 循环
t_for = timeit.timeit(
"s = 0\nfor i in range(n): s += i",
setup=f"n = {n}",
number=10
)
# while 循环
t_while = timeit.timeit(
"s = i = 0\nwhile i < n: s += i; i += 1",
setup=f"n = {n}",
number=10
)
print(f"for: {t_for:.3f}s")
print(f"while: {t_while:.3f}s")
# for 通常比 while 快 10-20%性能考量
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| if-else 判断 | O(1) | 一次条件计算 |
| if-elif-else (n 分支) | O(n) 最坏 | 线性检查直到命中 |
| match (字面量) | O(1) ~ O(n) | 简单情况等同于 if-elif 链 |
| match (序列/映射) | O(n) | 取决于模式复杂度 |
for item in iterable | O(n) | 遍历所有元素 |
range(n) 生成 | O(1) | range 是惰性对象,不实际生成所有数字 |
range(n) 遍历 | O(n) | 遍历时才逐个产生值 |
| for vs while (大循环) | for 略快 | for 用 C 级迭代器,无每次 Python 比较开销 |
| 守卫子句 vs 深层嵌套 | 相同 | 性能无差异,纯可读性优化 |
import timeit
# range 不占大量内存
import sys
r = range(10_000_000)
print(sys.getsizeof(r)) # 仅 48 字节!
# 等价 list 占用巨大内存
# l = list(range(10_000_000)) # ~80MB,不要执行
# if-elif 链性能
print(timeit.timeit(
"check(5)",
setup="""
def check(x):
if x == 0: return "a"
elif x == 1: return "b"
elif x == 2: return "c"
elif x == 3: return "d"
elif x == 4: return "e"
elif x == 5: return "f"
elif x == 6: return "g"
elif x == 7: return "h"
elif x == 8: return "i"
elif x == 9: return "j"
else: return "z"
""",
number=10_000_000
))
# → 匹配最后一个分支(x=5)需要 6 次比较知识关联
流程控制知识关联:
┌─────────────────┐
│ 条件表达式 │
│ 真值判断 │
│ (运算符章节) │
└─────────────────┘
│
┌─────────────┼─────────────┐
↓ ↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ if-else │ │ match │ │ 三元运算符│
│ 基础分支 │ │ 模式匹配 │ │ 紧凑表达式│
└───────────┘ └───────────┘ └───────────┘
│ │ │
↓ ↓ ↓
┌───────────────────────────────────────┐
│ 守卫子句(guard clause) │
│ 减少嵌套,提前返回 │
└───────────────────────────────────────┘
┌─────────────────┐
│ 可迭代对象 │
│ __iter__ │
│ __next__ │
└─────────────────┘
│
┌─────────────┼─────────────┐
↓ ↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ for 循环 │ │ while 循环│ │ range() │
│ 遍历迭代 │ │ 条件循环 │ │ 惰性序列 │
└───────────┘ └───────────┘ └───────────┘
│ │
↓ ↓
┌───────────────────────────────────────┐
│ 循环控制:break / continue / else │
│ 异常控制:try-except-else-finally │
└───────────────────────────────────────┘设计动机
| 设计选择 | 原因 | 影响 |
|---|---|---|
| Python 没有传统的 switch/case(3.10 之前) | 早期设计哲学:if-elif 够用 | 3.10 的 match 比传统 switch 更强大 |
match/case 不穿透(无 fall-through) | 避免 C 语言 switch 的常见 bug | 不需要 break,每个 case 独立 |
for-else 语法 | else 表示"正常结束(未被 break)" | 命名不直观,但提供了搜索模式的优雅写法 |
range 惰性生成 | 避免大列表内存占用 | range(10**9) 只占 ~48 字节 |
| break/continue 只影响最内层循环 | 简单明确 | 没有 goto/label,多层级跳出需标志变量或异常 |
本章小结
┌─────────────────────────────────────────────────────────────┐
│ 流程控制 知识要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 条件语句: │
│ ✓ if / if-else / if-elif-else │
│ ✓ 三元运算符:值 1 if 条件 else 值 2 │
│ ✓ match 语句(Python 3.10+):模式匹配 │
│ ✓ pass 语句:占位符,保持语法完整 │
│ │
│ 循环语句: │
│ ✓ for 循环:遍历序列 │
│ ✓ while 循环:条件循环 │
│ ✓ range():生成数字序列 │
│ │
│ 循环控制: │
│ ✓ break:跳出整个循环 │
│ ✓ continue:跳过本次循环 │
│ │
│ 实际应用: │
│ ✓ 用户交互、游戏循环、菜单系统 │
│ │
└─────────────────────────────────────────────────────────────┘