Skip to content

Latest commit

 

History

History
269 lines (189 loc) · 8.4 KB

05-control-flow.md

File metadata and controls

269 lines (189 loc) · 8.4 KB

控制流与推导式 — 条件、循环、推导式与 match-case

先理解,再动手

推导式是建立新容器的表达式,普通循环更适合多步骤副作用。生成器表达式则延迟生产,不是另一种已经填满的列表。

本节自测:筛出偶数并平方,输入空列表,再把列表推导式换生成器并消费两次。

预期结果与参考思路(先尝试再展开)

空输入得到空结果;生成器耗尽后再次消费为空,若需复用要重新创建或保存结果。

文档简介: 掌握 Python 条件与循环的全部写法,重点训练推导式这一 Python 标志性表达力工具,并学会 3.10+ 的 match-case 结构化分支

目标读者: 已掌握函数与类的开发者,想写出地道 Python 控制流的人

前置知识: 完成函数与类,理解可迭代对象概念

文档信息(用途、难度与维护记录)

📚 文档元数据

属性 内容
模块 10-python-discovery
象限 教程
难度 ⭐
标签 #控制流 #推导式 #match-case #迭代
更新日期 2026年9月

🎯 学习目标

完成本文档后,你将能够:

  • ✅ 用 if/elif/else 与三元表达式组织分支逻辑
  • ✅ 用 for/while 配合 enumerate、zip 优雅遍历
  • ✅ 写出列表/字典/集合/生成器四种推导式
  • ✅ 用 match-case 实现结构化模式匹配

1. 条件分支:Python 的真值美学

score = 87

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"          # 注意 elif 而非 else if
else:
    grade = "C"

# 三元表达式:值在前,条件在后
label = "及格" if score >= 60 else "不及格"

# 链式比较,数学式写法
if 0 <= score <= 100:
    print("有效分数")

match-case(3.10+)处理多分支结构与解构:

def handle(command: str) -> str:
    match command.split():
        case ["add" | "new", arg]:     # 或模式
            return f"添加 {arg}"
        case ["rm", *targets]:         # 序列解构模式
            return f"删除 {len(targets)} 项"
        case _:
            return "未知命令"

⚠️ case _ 是通配符,必须放最后;匹配字面量直接写值,匹配变量会被当作捕获名——要用点号访问的常量(如 Status.OK)才按值匹配。

def classify(command):
    match command.split():
        case ["add", name] if name:
            return f"add:{name}"
        case ["quit"]:
            return "quit"
        case _:
            return "unknown"

found = None
for value in [0, -1, 4]:
    if value > 0:
        found = value
        break
else:
    found = "missing"

assert (classify("add Ada"), classify("quit"), classify("add"), found) == ("add:Ada", "quit", "unknown", 4)
print("python-keywords: match case guard; for else; break")

该程序从正文原样提取并运行 match、case、if、for、break、else 与 return。 它只验证三条具体控制流路径;模式匹配的类模式、映射模式和异常传播需要另建输入验证。


2. for 循环:迭代即遍历

Python 的 for 遍历可迭代对象,不是下标:

langs = ["python", "go", "rust"]

for lang in langs:                  # 直接遍历元素
    print(lang)

for i, lang in enumerate(langs, start=1):   # 需要下标时
    print(f"{i}. {lang}")

for cn, en in zip(["一", "二"], [1, 2]):     # 并行遍历
    print(cn, en)

for k, v in {"py": 1991, "go": 2009}.items():  # 遍历字典
    print(k, v)

range 与循环控制:

for i in range(3):          # 0, 1, 2(含头不含尾)
    ...

for i in range(10, 0, -2):  # 10, 8, 6, 4, 2
    ...

while True:                 # while 适合"不确定次数"的场景
    cmd = input("> ")
    if cmd == "quit":
        break               # 跳出循环
    if not cmd:
        continue            # 跳过本轮
    print(cmd)

for-else:循环未被 break 打断时执行 else,常用于搜索:

for item in items:
    if item == target:
        print("找到了")
        break
else:
    print("没找到")          # 没被 break 才执行

💡 优先考虑"遍历元素"而非"遍历下标再取元素"。需要下标就用 enumerate,这正是 Python 惯用法与 C 风格循环的分水岭。


3. 推导式:Python 的标志表达力

推导式(comprehension)把"建列表的循环"压缩成一行;其性能常常不错,但是否更快取决于工作量、解释器和等价循环的具体写法:

nums = [1, 2, 3, 4, 5, 6]

# 列表推导式:[表达式 for 变量 in 可迭代 if 条件]
squares = [n * n for n in nums]                 # [1, 4, 9, 16, 25, 36]
evens = [n for n in nums if n % 2 == 0]         # [2, 4, 6]
matrix_flat = [x for row in [[1, 2], [3, 4]] for x in row]   # [1, 2, 3, 4]

# 字典推导式
len_map = {w: len(w) for w in ["py", "go"]}     # {'py': 2, 'go': 2}
rev = {v: k for k, v in len_map.items()}        # 键值互换

# 集合推导式
unique_len = {len(w) for w in ["py", "go", "rust"]}   # {2, 4}

# 生成器表达式:圆括号,惰性求值,省内存
total = sum(n * n for n in range(1_000_000))    # 不建中间列表

⚠️ 推导式超过两层 for 或两个 if 就该换回普通循环——可读性优先,一行不是目的。

📖 四种推导式的语法对照、条件/嵌套写法与生成器惰性语义,见 推导式速查。


4. 推导式实战:数据清洗三例

# 例1:清洗用户输入
raw = ["  Ada ", "", "BOB", None, "cleo"]
names = [s.strip().title() for s in raw if s]        # ['Ada', 'Bob', 'Cleo']

# 例2:条件表达式做分支映射
grades = {n: ("pass" if s >= 60 else "fail") for n, s in {"a": 70, "b": 50}.items()}

# 例3:展平嵌套 JSON 字段
records = [{"tags": ["web", "api"]}, {"tags": ["db"]}]
all_tags = {t for r in records for t in r["tags"]}   # {'web', 'api', 'db'}

any()/all() 配生成器表达式是集合判断的地道写法:

has_admin = any(u["role"] == "admin" for u in users)
all_valid = all(len(u["name"]) > 0 for u in users)

✅ 最佳实践

需要索引和值时用 enumerate,需要键值时用 items,可使循环目的更明显;确实要按位置修改或访问邻居时,索引循环仍然合理。推导式适合短的变换,复杂条件和副作用用普通循环更易追踪。

生成器避免提前保存所有中间项,但只消费一次且最终聚合仍可能需要内存。for-else 表示循环未被 break 终止,不是“循环条件为假才开始执行”。用找到、未找到和空输入三个例子验证理解。


❓ 常见问题

Q1: 推导式和 map/filter 怎么选?

A: 新代码统一用推导式,可读性更好;map/filter 只在读老代码和已有函数可直接复用时考虑。

Q2: match-case 就是 switch 吗?

A: 远不止——它能解构序列、映射,配合守卫 if 子句做结构化匹配,本质是模式匹配而非值跳转。


🎯 练习与实践

练习一:单词统计

  1. 给定 text = "the quick the lazy the dog",用推导式统计每个单词出现次数(dict 推导式 + .split())
  2. 用集合推导式输出出现超过一次的单词

练习二:重构为 match-case

  1. 把下面的 if 链改写成 match-case(含序列解构):
if cmd == "add":
    op, *rest = args
elif cmd == "rm":
    targets = args
else:
    ...
  1. 为 add 分支加守卫:args 为空时输出提示

评估标准:两个练习均用推导式/match 完成,单函数不超过 10 行,ruff 通过。


🔗 相关文档

阅读导航

本模块理解地图 · 完整目录与版本 · 通用术语