第 2 卷:函数、模块与推导式
学习方式:严格按顺序阅读。每个代码块只使用当前单元或此前单元已经讲过的语法。
本卷项目线:
taskflow/milestones/v02_functional_tasks.py --demo完成本卷后:进入第 3 卷。
本卷说明
本卷把第一卷的过程式代码拆成可复用函数,并引入模块、解包、推导式和结构化分支。从这里开始,项目线会从单文件脚本演进为可测试的函数集合。
本卷知识单元
- [2.1 函数定义、调用与返回值](#2.1 函数定义、调用与返回值 "#21-%E5%87%BD%E6%95%B0%E5%AE%9A%E4%B9%89%E8%B0%83%E7%94%A8%E4%B8%8E%E8%BF%94%E5%9B%9E%E5%80%BC")
- [2.2 参数形态与调用约定](#2.2 参数形态与调用约定 "#22-%E5%8F%82%E6%95%B0%E5%BD%A2%E6%80%81%E4%B8%8E%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A")
- [2.3 作用域、闭包与迟绑定](#2.3 作用域、闭包与迟绑定 "#23-%E4%BD%9C%E7%94%A8%E5%9F%9F%E9%97%AD%E5%8C%85%E4%B8%8E%E8%BF%9F%E7%BB%91%E5%AE%9A")
- [2.4 解包、星号表达式与函数调用展开](#2.4 解包、星号表达式与函数调用展开 "#24-%E8%A7%A3%E5%8C%85%E6%98%9F%E5%8F%B7%E8%A1%A8%E8%BE%BE%E5%BC%8F%E4%B8%8E%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E5%B1%95%E5%BC%80")
- [2.5 列表、字典与集合推导式](#2.5 列表、字典与集合推导式 "#25-%E5%88%97%E8%A1%A8%E5%AD%97%E5%85%B8%E4%B8%8E%E9%9B%86%E5%90%88%E6%8E%A8%E5%AF%BC%E5%BC%8F")
- [2.6 结构化模式匹配 match/case](#2.6 结构化模式匹配 match/case "#26-%E7%BB%93%E6%9E%84%E5%8C%96%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D-matchcase")
- [2.7 enumerate、zip、sorted 与聚合函数](#2.7 enumerate、zip、sorted 与聚合函数 "#27-enumeratezipsorted-%E4%B8%8E%E8%81%9A%E5%90%88%E5%87%BD%E6%95%B0")
- [2.8 模块、导入与脚本入口](#2.8 模块、导入与脚本入口 "#28-%E6%A8%A1%E5%9D%97%E5%AF%BC%E5%85%A5%E4%B8%8E%E8%84%9A%E6%9C%AC%E5%85%A5%E5%8F%A3")
- [2.9 函数与模块项目线:TaskFlow v02](#2.9 函数与模块项目线:TaskFlow v02 "#29-%E5%87%BD%E6%95%B0%E4%B8%8E%E6%A8%A1%E5%9D%97%E9%A1%B9%E7%9B%AE%E7%BA%BFtaskflow-v02")
- [2.10 嵌套列表与字典的数据转换](#2.10 嵌套列表与字典的数据转换 "#210-%E5%B5%8C%E5%A5%97%E5%88%97%E8%A1%A8%E4%B8%8E%E5%AD%97%E5%85%B8%E7%9A%84%E6%95%B0%E6%8D%AE%E8%BD%AC%E6%8D%A2")
- [2.11 函数接口设计与缺失值](#2.11 函数接口设计与缺失值 "#211-%E5%87%BD%E6%95%B0%E6%8E%A5%E5%8F%A3%E8%AE%BE%E8%AE%A1%E4%B8%8E%E7%BC%BA%E5%A4%B1%E5%80%BC")
- [2.12 模块拆分、导入边界与 all](#2.12 模块拆分、导入边界与 all "#212-%E6%A8%A1%E5%9D%97%E6%8B%86%E5%88%86%E5%AF%BC%E5%85%A5%E8%BE%B9%E7%95%8C%E4%B8%8E-all")
- [2.13 分派表与配置驱动编程](#2.13 分派表与配置驱动编程 "#213-%E5%88%86%E6%B4%BE%E8%A1%A8%E4%B8%8E%E9%85%8D%E7%BD%AE%E9%A9%B1%E5%8A%A8%E7%BC%96%E7%A8%8B")
2.1 函数定义、调用与返回值
前置知识:第 1 卷全部基础语法。
概念 :函数把一段可复用逻辑命名为一个调用单元。def 定义函数,调用时把实参绑定到形参;return 结束函数并把一个对象交回调用方。没有显式 return 时返回 None。
本单元新语法
| 语法形式 | 含义 |
|---|---|
def name(params): |
定义函数 |
return value |
返回一个对象并结束函数 |
name(args) |
调用函数 |
pass |
语法占位,什么都不做 |
None |
没有返回值时的结果 |
最小示例
最小示例 1:定义与调用
python
# 示例目标:定义与调用
# 前置知识:第 1 卷全部基础语法。
# 代码状态:完整可运行
def greet(name):
return "hello " + name
message = greet("ann")
print(message)
# 输出: hello ann
# 示例类型:最小示例
最小示例 2:没有 return
python
# 示例目标:没有 return
# 前置知识:第 1 卷全部基础语法。
# 代码状态:完整可运行
def log_only(text):
print(text)
result = log_only("saved")
print(result)
# 输出: saved
# 输出: None
# 示例类型:最小示例
实际场景示例
把输入校验、数据转换、业务规则和 I/O 分成函数,能让每一部分单独测试和复用。
python
# 示例目标:任务标题规范化
# 前置知识:第 1 卷全部基础语法。
# 代码状态:片段/按前置知识运行
def normalize_title(title):
cleaned = title.strip()
if not cleaned:
return None
return cleaned
print(normalize_title(" read "))
# 输出: read
# 示例类型:实际场景
关联/组合
组合 1:提前 return
python
# 示例目标:提前 return
# 前置知识:第 1 卷全部基础语法。
# 代码状态:片段/按前置知识运行
def classify(score):
if score >= 60:
return "pass"
return "fail"
print(classify(80))
print(classify(50))
# 输出: pass
# 输出: fail
# 示例类型:关联/组合
组合 2:函数返回元组
python
# 示例目标:函数返回元组
# 前置知识:第 1 卷全部基础语法。
# 代码状态:片段/按前置知识运行
def min_max(values):
return min(values), max(values)
low, high = min_max([3, 1, 4])
print(low, high)
# 输出: 1 4
# 示例类型:关联/组合
易错/易混
易错 1:把 return 写成 print
错误代码:
python
# 示例目标:把 return 写成 print(错误示范)
# 前置知识:第 1 卷全部基础语法。
# 代码状态:片段/按前置知识运行
def double(value):
print(value * 2)
result = double(3)
print(result)
# 示例类型:错误示范
实际结果:先输出 6,再输出 None;调用方拿不到计算结果。
修正代码:
python
# 示例目标:把 return 写成 print(修正)
# 前置知识:第 1 卷全部基础语法。
# 代码状态:片段/按前置知识运行
def double(value):
return value * 2
result = double(3)
print(result)
# 输出: 6
# 示例类型:易错修正
原因:print 只负责输出,return 才把值交给调用方。
易错 2:在 return 后继续写逻辑
错误代码:
python
# 示例目标:在 return 后继续写逻辑(错误示范)
# 前置知识:第 1 卷全部基础语法。
# 代码状态:片段/按前置知识运行
def check(value):
return value > 0
print("unreachable")
# 示例类型:错误示范
实际结果:print 永远不执行。
修正代码:
python
# 示例目标:在 return 后继续写逻辑(修正)
# 前置知识:第 1 卷全部基础语法。
# 代码状态:片段/按前置知识运行
def check(value):
if value <= 0:
return False
print("positive")
return True
print(check(1))
# 输出: positive
# 输出: True
# 示例类型:易错修正
原因:return 立即结束函数;需要后续逻辑时先处理失败分支。
练习
- 定义
square(n)返回平方。 - 定义
label(score),60 分及以上返回 pass,否则返回 fail。
2.2 参数形态与调用约定
前置知识:2.1 函数定义与 return。
概念 :Python 支持位置参数、关键字参数、默认参数、*args 和 **kwargs,还可以用 / 限定仅位置参数、用 * 限定仅关键字参数。默认值在函数定义时求值一次。
本单元新语法
| 语法形式 | 含义 |
|---|---|
def f(a, b=1): |
带默认值 |
f(1, b=2) |
位置和关键字混合 |
def f(*args): |
收集多余位置参数为元组 |
def f(**kwargs): |
收集多余关键字参数为字典 |
def f(a, /, *, b): |
仅位置与仅关键字参数 |
最小示例
最小示例 1:默认与关键字
python
# 示例目标:默认与关键字
# 前置知识:2.1 函数定义与 return。
# 代码状态:完整可运行
def create(title, priority="normal"):
return {"title": title, "priority": priority}
print(create("Read"))
print(create("Ship", priority="high"))
# 输出: {'title': 'Read', 'priority': 'normal'}
# 输出: {'title': 'Ship', 'priority': 'high'}
# 示例类型:最小示例
最小示例 2:可变参数
python
# 示例目标:可变参数
# 前置知识:2.1 函数定义与 return。
# 代码状态:完整可运行
def collect(*args, **kwargs):
return args, kwargs
print(collect(1, 2, mode="demo"))
# 输出: ((1, 2), {'mode': 'demo'})
# 示例类型:最小示例
实际场景示例
公共 API 用明确参数,框架回调和包装器才用 *args/**kwargs 转发;参数过多时改为配置对象。
python
# 示例目标:仅关键字选项
# 前置知识:2.1 函数定义与 return。
# 代码状态:片段/按前置知识运行
def create_task(title, *, priority="normal"):
return title, priority
print(create_task("Read", priority="high"))
# 输出: ('Read', 'high')
# 示例类型:实际场景
关联/组合
组合 1:位置参数顺序
python
# 示例目标:位置参数顺序
# 前置知识:2.1 函数定义与 return。
# 代码状态:片段/按前置知识运行
def divide(a, b):
return a / b
print(divide(10, 2))
# 输出: 5.0
# 示例类型:关联/组合
组合 2:字典转发参数
python
# 示例目标:字典转发参数
# 前置知识:2.1 函数定义与 return。
# 代码状态:片段/按前置知识运行
def show(title, done=False):
print(title, done)
options = {"title": "Read", "done": True}
show(**options)
# 输出: Read True
# 示例类型:关联/组合
易错/易混
易错 1:可变默认值
错误代码:
python
# 示例目标:可变默认值(错误示范)
# 前置知识:2.1 函数定义与 return。
# 代码状态:片段/按前置知识运行
def add_tag(tag, tags=[]):
tags.append(tag)
return tags
print(add_tag("a"))
print(add_tag("b"))
# 示例类型:错误示范
实际结果:第二次调用仍使用同一个列表,输出包含 a 和 b。
修正代码:
python
# 示例目标:可变默认值(修正)
# 前置知识:2.1 函数定义与 return。
# 代码状态:片段/按前置知识运行
def add_tag(tag, tags=None):
if tags is None:
tags = []
tags.append(tag)
return tags
print(add_tag("a"))
print(add_tag("b"))
# 输出: ['a']
# 输出: ['b']
# 示例类型:易错修正
原因:默认值只求值一次;可变默认值会跨调用共享。
易错 2:未知关键字参数
错误代码:
python
# 示例目标:未知关键字参数(错误示范)
# 前置知识:2.1 函数定义与 return。
# 代码状态:片段/按前置知识运行
def show(title):
print(title)
show(titel="Read")
# 示例类型:错误示范
实际结果:抛出 TypeError:unexpected keyword argument。
修正代码:
python
# 示例目标:未知关键字参数(修正)
# 前置知识:2.1 函数定义与 return。
# 代码状态:片段/按前置知识运行
def show(title):
print(title)
show(title="Read")
# 输出: Read
# 示例类型:易错修正
原因:关键字名必须与形参名一致,拼写错误不会被自动修正。
练习
- 给
create(title, priority="normal")增加仅关键字参数tags。 - 用
**options调用一个接收title和done的函数。
2.3 作用域、闭包与迟绑定
前置知识:2.1 函数;1.9 可变性。
概念 :名字查找遵循局部、外层函数、全局、内置的顺序。内层函数可以读取外层变量;要修改外层变量必须声明 nonlocal。闭包捕获的是变量,而不是变量在定义瞬间的值。
本单元新语法
| 语法形式 | 含义 |
|---|---|
local scope |
函数内赋值的名字默认是局部变量 |
global x |
声明修改模块级名字,少用 |
nonlocal x |
声明修改最近外层函数变量 |
closure |
内层函数保留外层变量引用 |
late binding |
闭包在调用时读取变量当前值 |
最小示例
最小示例 1:nonlocal 计数
python
# 示例目标:nonlocal 计数
# 前置知识:2.1 函数;1.9 可变性。
# 代码状态:完整可运行
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter())
print(counter())
# 输出: 1
# 输出: 2
# 示例类型:最小示例
最小示例 2:闭包读取外层变量
python
# 示例目标:闭包读取外层变量
# 前置知识:2.1 函数;1.9 可变性。
# 代码状态:完整可运行
def make_prefix(prefix):
def format_title(title):
return prefix + title
return format_title
format_title = make_prefix("Task: ")
print(format_title("Read"))
# 输出: Task: Read
# 示例类型:最小示例
实际场景示例
工厂函数、依赖注入和回调需要闭包;跨请求状态不应放在全局或闭包里,应使用显式上下文或数据库。
python
# 示例目标:生成固定前缀的格式化器
# 前置知识:2.1 函数;1.9 可变性。
# 代码状态:片段/按前置知识运行
def make_formatter(prefix):
def render(value):
return f"{prefix}{value}"
return render
render = make_formatter("id=")
print(render("t1"))
# 输出: id=t1
# 示例类型:实际场景
关联/组合
组合 1:局部变量遮蔽
python
# 示例目标:局部变量遮蔽
# 前置知识:2.1 函数;1.9 可变性。
# 代码状态:片段/按前置知识运行
value = 10
def show():
value = 20
print(value)
show()
print(value)
# 输出: 20
# 输出: 10
# 示例类型:关联/组合
组合 2:闭包共享状态
python
# 示例目标:闭包共享状态
# 前置知识:2.1 函数;1.9 可变性。
# 代码状态:片段/按前置知识运行
def make_box():
items = []
def add(item):
items.append(item)
return items
return add
add = make_box()
add("a")
print(add("b"))
# 输出: ['a', 'b']
# 示例类型:关联/组合
易错/易混
易错 1:内层赋值未声明 nonlocal
错误代码:
python
# 示例目标:内层赋值未声明 nonlocal(错误示范)
# 前置知识:2.1 函数;1.9 可变性。
# 代码状态:片段/按前置知识运行
def make_counter():
count = 0
def increment():
count += 1
return count
return increment
# 示例类型:错误示范
实际结果:调用时抛出 UnboundLocalError;count 被视为局部变量。
修正代码:
python
# 示例目标:内层赋值未声明 nonlocal(修正)
# 前置知识:2.1 函数;1.9 可变性。
# 代码状态:片段/按前置知识运行
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
print(make_counter()())
# 输出: 1
# 示例类型:易错修正
原因:内层要修改外层变量时必须声明 nonlocal。
易错 2:误以为闭包保存定义时的值
错误代码:
python
# 示例目标:误以为闭包保存定义时的值(错误示范)
# 前置知识:2.1 函数;1.9 可变性。
# 代码状态:片段/按前置知识运行
def make_functions():
funcs = []
for number in range(3):
def show():
return number
funcs.append(show)
return funcs
funcs = make_functions()
values = []
for fn in funcs:
values.append(fn())
print(values)
# 示例类型:错误示范
实际结果:输出 2, 2, 2;闭包在调用时读取 number 的最后值。
修正代码:
python
# 示例目标:误以为闭包保存定义时的值(修正)
# 前置知识:2.1 函数;1.9 可变性。
# 代码状态:片段/按前置知识运行
def make_functions():
funcs = []
for number in range(3):
def show(value=number):
return value
funcs.append(show)
return funcs
funcs = make_functions()
values = []
for fn in funcs:
values.append(fn())
print(values)
# 输出: [0, 1, 2]
# 示例类型:易错修正
原因:用默认参数固定当前值,避免迟绑定。
练习
- 写一个闭包计数器,每次调用加一并返回。
- 说明为什么
for中创建的闭包可能都返回最后一个值。
2.4 解包、星号表达式与函数调用展开
前置知识:1.7 序列;1.8 字典;2.2 参数。
概念 :Python 用 * 展开可迭代对象、用 ** 展开字典。它们可用于赋值收集、列表/字典构造和函数调用,但同一个调用中不能重复传同一个关键字。
本单元新语法
| 语法形式 | 含义 |
|---|---|
a, *rest = items |
收集序列剩余项 |
[*a, *b] |
列表展开合并 |
{**a, **b} |
字典展开合并 |
f(*args) |
把序列展开成位置参数 |
f(**kwargs) |
把字典展开成关键字参数 |
最小示例
最小示例 1:列表与字典展开
python
# 示例目标:列表与字典展开
# 前置知识:1.7 序列;1.8 字典;2.2 参数。
# 代码状态:完整可运行
left = [1, 2]
right = [3]
print([*left, *right])
print({**{"a": 1}, **{"b": 2}})
# 输出: [1, 2, 3]
# 输出: {'a': 1, 'b': 2}
# 示例类型:最小示例
最小示例 2:调用时展开
python
# 示例目标:调用时展开
# 前置知识:1.7 序列;1.8 字典;2.2 参数。
# 代码状态:完整可运行
def add(a, b):
return a + b
args = [2, 3]
print(add(*args))
# 输出: 5
# 示例类型:最小示例
实际场景示例
合并默认配置、把查询参数传给函数、构造新列表时都可以使用展开,但不要为了短而牺牲可读性。
python
# 示例目标:合并默认配置
# 前置知识:1.7 序列;1.8 字典;2.2 参数。
# 代码状态:片段/按前置知识运行
defaults = {"priority": "normal", "done": False}
overrides = {"priority": "high"}
config = {**defaults, **overrides}
print(config)
# 输出: {'priority': 'high', 'done': False}
# 示例类型:实际场景
关联/组合
组合 1:收集剩余参数
python
# 示例目标:收集剩余参数
# 前置知识:1.7 序列;1.8 字典;2.2 参数。
# 代码状态:片段/按前置知识运行
first, *middle = [1, 2, 3, 4]
print(first, middle)
# 输出: 1 [2, 3, 4]
# 示例类型:关联/组合
组合 2:关键字展开
python
# 示例目标:关键字展开
# 前置知识:1.7 序列;1.8 字典;2.2 参数。
# 代码状态:片段/按前置知识运行
def show(title, done=False):
print(title, done)
options = {"title": "Read", "done": True}
show(**options)
# 输出: Read True
# 示例类型:关联/组合
易错/易混
易错 1:重复关键字
错误代码:
python
# 示例目标:重复关键字(错误示范)
# 前置知识:1.7 序列;1.8 字典;2.2 参数。
# 代码状态:片段/按前置知识运行
def show(a):
print(a)
show(1, **{"a": 2})
# 示例类型:错误示范
实际结果:抛出 TypeError:multiple values for argument 'a'。
修正代码:
python
# 示例目标:重复关键字(修正)
# 前置知识:1.7 序列;1.8 字典;2.2 参数。
# 代码状态:片段/按前置知识运行
def show(a):
print(a)
show(1)
# 输出: 1
# 示例类型:易错修正
原因:位置参数和字典展开可能绑定同一个形参,必须避免重复。
易错 2:把字符串展开成列表元素
错误代码:
python
# 示例目标:把字符串展开成列表元素(错误示范)
# 前置知识:1.7 序列;1.8 字典;2.2 参数。
# 代码状态:片段/按前置知识运行
print([*"abc"])
# 示例类型:错误示范
实际结果:输出 'a', 'b', 'c',不是 'abc'。
修正代码:
python
# 示例目标:把字符串展开成列表元素(修正)
# 前置知识:1.7 序列;1.8 字典;2.2 参数。
# 代码状态:片段/按前置知识运行
print(["abc"])
# 输出: ['abc']
# 示例类型:易错修正
原因:星号会展开可迭代对象的元素。
练习
- 用
*合并两个列表。 - 把字典
options展开传给函数show(title, done=False)。
2.5 列表、字典与集合推导式
前置知识:2.1 函数;2.4 解包;1.10 循环。
概念:推导式用表达式从可迭代对象构造容器。列表推导式产生列表,字典推导式产生键值映射,集合推导式去重;生成器表达式使用圆括号并惰性产出。
本单元新语法
| 语法形式 | 含义 |
|---|---|
[expr for x in items if cond] |
列表推导式 |
{k: v for x in items} |
字典推导式 |
{expr for x in items} |
集合推导式 |
(expr for x in items) |
生成器表达式 |
[expr for x in a for y in b] |
多循环推导式 |
最小示例
最小示例 1:过滤与转换
python
# 示例目标:过滤与转换
# 前置知识:2.1 函数;2.4 解包;1.10 循环。
# 代码状态:完整可运行
numbers = [1, 2, 3, 4]
squares = [n * n for n in numbers if n % 2 == 0]
print(squares)
# 输出: [4, 16]
# 示例类型:最小示例
最小示例 2:字典与集合
python
# 示例目标:字典与集合
# 前置知识:2.1 函数;2.4 解包;1.10 循环。
# 代码状态:完整可运行
names = ["ann", "bob"]
print({name: len(name) for name in names})
print({name[0] for name in names})
# 输出: {'ann': 3, 'bob': 3}
# 输出: {'a', 'b'}
# 示例类型:最小示例
实际场景示例
清洗 API 列表、建立索引和去重标签时,推导式比手写 append 循环更直接;复杂副作用仍应使用普通循环。
python
# 示例目标:任务标题索引
# 前置知识:2.1 函数;2.4 解包;1.10 循环。
# 代码状态:片段/按前置知识运行
tasks = [{"id": "a", "title": "Read"}, {"id": "b", "title": "Write"}]
index = {task["id"]: task["title"] for task in tasks}
print(index)
# 输出: {'a': 'Read', 'b': 'Write'}
# 示例类型:实际场景
关联/组合
组合 1:生成器表达式求和
python
# 示例目标:生成器表达式求和
# 前置知识:2.1 函数;2.4 解包;1.10 循环。
# 代码状态:片段/按前置知识运行
print(sum(n * n for n in range(5)))
# 输出: 30
# 示例类型:关联/组合
组合 2:多循环顺序
python
# 示例目标:多循环顺序
# 前置知识:2.1 函数;2.4 解包;1.10 循环。
# 代码状态:片段/按前置知识运行
print([(x, y) for x in range(2) for y in "ab"])
# 输出: [(0, 'a'), (0, 'b'), (1, 'a'), (1, 'b')]
# 示例类型:关联/组合
易错/易混
易错 1:把过滤条件写错位置
错误代码:
python
# 示例目标:把过滤条件写错位置(错误示范)
# 前置知识:2.1 函数;2.4 解包;1.10 循环。
# 代码状态:片段/按前置知识运行
numbers = [1, 2, 3]
print([n * n if n > 1 for n in numbers])
# 示例类型:错误示范
实际结果:语法错误:条件表达式不能替代过滤条件。
修正代码:
python
# 示例目标:把过滤条件写错位置(修正)
# 前置知识:2.1 函数;2.4 解包;1.10 循环。
# 代码状态:片段/按前置知识运行
numbers = [1, 2, 3]
print([n * n for n in numbers if n > 1])
# 输出: [4, 9]
# 示例类型:易错修正
原因:过滤写在 for 后;转换表达式写在最前面。
易错 2:推导式里做 I/O
错误代码:
python
# 示例目标:推导式里做 I/O(错误示范)
# 前置知识:2.1 函数;2.4 解包;1.10 循环。
# 代码状态:片段/按前置知识运行
results = [save(item) for item in items]
# 示例类型:错误示范
实际结果:虽然可运行,但每项都立即写库,异常和事务边界难以控制。
修正代码:
python
# 示例目标:推导式里做 I/O(修正)
# 前置知识:2.1 函数;2.4 解包;1.10 循环。
# 代码状态:片段/按前置知识运行
results = []
for item in items:
results.append(save(item))
# 示例类型:易错修正
原因:有副作用时用显式循环,便于日志、回滚和错误处理。
练习
- 用列表推导式取出 1 到 10 中的偶数。
- 用字典推导式建立
id -> title映射。
2.6 结构化模式匹配 match/case
前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
概念 :match/case 根据数据结构进行分支。它可以匹配字面量、序列、字典、类实例和嵌套结构,case _ 是兜底,if 可加守卫。它按从上到下的顺序匹配。
本单元新语法
| 语法形式 | 含义 |
|---|---|
match value: |
开始模式匹配 |
case {"type": kind}: |
映射模式 |
case [first, *rest]: |
序列模式 |
case _: |
兜底模式 |
case x if cond: |
守卫条件 |
最小示例
最小示例 1:映射模式
python
# 示例目标:映射模式
# 前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
# 代码状态:完整可运行
def route(event):
match event:
case {"type": "user", "name": name}:
return "user:" + name
case _:
return "unknown"
print(route({"type": "user", "name": "ann"}))
# 输出: user:ann
# 示例类型:最小示例
最小示例 2:序列模式
python
# 示例目标:序列模式
# 前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
# 代码状态:完整可运行
def first_two(items):
match items:
case [a, b, *_]:
return a + b
case _:
return 0
print(first_two([1, 2, 3]))
# 输出: 3
# 示例类型:最小示例
实际场景示例
事件路由、命令分发和 API 消息处理适合模式匹配;匹配的是结构,不是替代所有 if。
python
# 示例目标:任务命令分发
# 前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
# 代码状态:片段/按前置知识运行
def handle(command):
match command:
case {"action": "add", "title": title}:
return "add " + title
case {"action": "list"}:
return "list"
case _:
return "unknown"
print(handle({"action": "add", "title": "Read"}))
# 输出: add Read
# 示例类型:实际场景
关联/组合
组合 1:守卫条件
python
# 示例目标:守卫条件
# 前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
# 代码状态:片段/按前置知识运行
def bucket(value):
match value:
case n if n < 0:
return "negative"
case 0:
return "zero"
case _:
return "positive"
print(bucket(-1))
# 输出: negative
# 示例类型:关联/组合
组合 2:字典模式允许额外键
python
# 示例目标:字典模式允许额外键
# 前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
# 代码状态:片段/按前置知识运行
match {"type": "user", "name": "ann", "extra": True}:
case {"type": "user", "name": name}:
print(name)
# 输出: ann
# 示例类型:关联/组合
易错/易混
易错 1:捕获变量写成兜底
错误代码:
python
# 示例目标:捕获变量写成兜底(错误示范)
# 前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
# 代码状态:片段/按前置知识运行
value = 3
match value:
case result:
print(result)
# 示例类型:错误示范
实际结果:输出 3;result 捕获任何值,后面的分支永远不可达。
修正代码:
python
# 示例目标:捕获变量写成兜底(修正)
# 前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
# 代码状态:片段/按前置知识运行
value = 3
match value:
case 1:
print("one")
case _:
print("other")
# 输出: other
# 示例类型:易错修正
原因:单个名字是捕获模式;兜底应使用 _。
易错 2:忘记处理未知结构
错误代码:
python
# 示例目标:忘记处理未知结构(错误示范)
# 前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
# 代码状态:片段/按前置知识运行
def route(event):
match event:
case {"type": "user"}:
return "user"
print(route({"type": "order"}))
# 示例类型:错误示范
实际结果:返回 None,调用方可能后续崩溃。
修正代码:
python
# 示例目标:忘记处理未知结构(修正)
# 前置知识:2.1 函数;2.5 容器推导式;1.4 条件。
# 代码状态:片段/按前置知识运行
def route(event):
match event:
case {"type": "user"}:
return "user"
case _:
return "unknown"
print(route({"type": "order"}))
# 输出: unknown
# 示例类型:易错修正
原因:外部输入的分发函数应有明确兜底。
练习
- 用 match 判断列表是否为空并打印 empty/non-empty。
- 匹配字典
{"type": "user", "name": "ann"}并提取 name。
2.7 enumerate、zip、sorted 与聚合函数
前置知识:2.1 函数;2.5 推导式;1.10 循环。
概念 :这些内置函数把常见循环意图写成声明式接口:enumerate 同时给索引和值,zip 并行迭代,sorted 返回新列表,any/all 聚合布尔条件。
本单元新语法
| 语法形式 | 含义 |
|---|---|
enumerate(items, start=0) |
索引与值 |
zip(a, b) |
并行组合多个可迭代对象 |
sorted(items, key=..., reverse=True) |
返回排序后的新列表 |
any(iterable) |
是否存在真值 |
all(iterable) |
是否全部为真值 |
最小示例
最小示例 1:索引与并行迭代
python
# 示例目标:索引与并行迭代
# 前置知识:2.1 函数;2.5 推导式;1.10 循环。
# 代码状态:完整可运行
names = ["ann", "bob"]
for index, name in enumerate(names, start=1):
print(index, name)
# 输出: 1 ann
# 输出: 2 bob
# 示例类型:最小示例
最小示例 2:排序与聚合
python
# 示例目标:排序与聚合
# 前置知识:2.1 函数;2.5 推导式;1.10 循环。
# 代码状态:完整可运行
numbers = [3, 1, 4]
print(sorted(numbers))
print(any(n > 3 for n in numbers))
print(all(n > 0 for n in numbers))
# 输出: [1, 3, 4]
# 输出: True
# 输出: True
# 示例类型:最小示例
实际场景示例
分页展示、批量校验、排序任务和合并两个数据源时,这些工具比手工维护索引更清晰。
python
# 示例目标:按长度排序标签
# 前置知识:2.1 函数;2.5 推导式;1.10 循环。
# 代码状态:片段/按前置知识运行
tags = ["work", "a", "urgent"]
print(sorted(tags, key=len, reverse=True))
# 输出: ['urgent', 'work', 'a']
# 示例类型:实际场景
关联/组合
组合 1:zip 截断到最短
python
# 示例目标:zip 截断到最短
# 前置知识:2.1 函数;2.5 推导式;1.10 循环。
# 代码状态:片段/按前置知识运行
print(list(zip([1, 2, 3], ["a", "b"])))
# 输出: [(1, 'a'), (2, 'b')]
# 示例类型:关联/组合
组合 2:enumerate 生成索引
python
# 示例目标:enumerate 生成索引
# 前置知识:2.1 函数;2.5 推导式;1.10 循环。
# 代码状态:片段/按前置知识运行
print(list(enumerate("ab")))
# 输出: [(0, 'a'), (1, 'b')]
# 示例类型:关联/组合
易错/易混
易错 1:把 sorted 当原地排序
错误代码:
python
# 示例目标:把 sorted 当原地排序(错误示范)
# 前置知识:2.1 函数;2.5 推导式;1.10 循环。
# 代码状态:片段/按前置知识运行
items = [3, 1]
result = sorted(items)
print(items, result)
# 示例类型:错误示范
实际结果:输出 3, 1 1, 3;sorted 不修改原列表。
修正代码:
python
# 示例目标:把 sorted 当原地排序(修正)
# 前置知识:2.1 函数;2.5 推导式;1.10 循环。
# 代码状态:片段/按前置知识运行
items = [3, 1]
items.sort()
print(items)
# 输出: [1, 3]
# 示例类型:易错修正
原因:需要新列表用 sorted,原地修改用 list.sort。
易错 2:zip 长度不一致仍假设完整
错误代码:
python
# 示例目标:zip 长度不一致仍假设完整(错误示范)
# 前置知识:2.1 函数;2.5 推导式;1.10 循环。
# 代码状态:片段/按前置知识运行
left = [1, 2]
right = ["a"]
print(list(zip(left, right)))
# 示例类型:错误示范
实际结果:只得到一组,第三组不会补 None。
修正代码:
python
# 示例目标:zip 长度不一致仍假设完整(修正)
# 前置知识:2.1 函数;2.5 推导式;1.10 循环。
# 代码状态:片段/按前置知识运行
left = [1, 2]
right = ["a"]
pairs = list(zip(left, right))
pairs.append((left[1], None))
print(pairs)
# 输出: [(1, 'a'), (2, None)]
# 示例类型:易错修正
原因:默认 zip 截断;需要补齐时显式处理剩余元素,或在学到 itertools 后使用 zip_longest。
练习
- 用 enumerate 打印带编号的任务。
- 判断列表是否至少有一个负数。
2.8 模块、导入与脚本入口
前置知识:2.1 函数;第 1 卷 import 基本形式。
概念 :模块是一个 .py 文件,包是带层级的模块集合。导入会执行模块顶层代码并缓存模块对象;if __name__ == "__main__": 区分直接运行和被导入。
本单元新语法
| 语法形式 | 含义 |
|---|---|
import math |
导入模块 |
from math import sqrt |
导入模块中的名字 |
import package.module |
导入包内模块 |
__name__ |
当前模块名 |
if __name__ == "__main__": |
脚本入口保护 |
最小示例
最小示例 1:标准库导入
python
# 示例目标:标准库导入
# 前置知识:2.1 函数;第 1 卷 import 基本形式。
# 代码状态:完整可运行
import math
print(math.sqrt(9))
# 输出: 3.0
# 示例类型:最小示例
最小示例 2:模块名与入口
python
# 示例目标:模块名与入口
# 前置知识:2.1 函数;第 1 卷 import 基本形式。
# 代码状态:完整可运行
print(__name__)
if __name__ == "__main__":
print("running as script")
# 直接运行时通常输出:
# __main__
# running as script
# 示例类型:最小示例
实际场景示例
把业务逻辑放进模块,把启动参数和依赖组装放在入口;测试导入模块时不应产生建库、发网络请求等副作用。
python
# 示例目标:从标准库导入工具
# 前置知识:2.1 函数;第 1 卷 import 基本形式。
# 代码状态:片段/按前置知识运行
from math import sqrt
print(sqrt(16))
# 输出: 4.0
# 示例类型:实际场景
关联/组合
组合 1:别名导入
python
# 示例目标:别名导入
# 前置知识:2.1 函数;第 1 卷 import 基本形式。
# 代码状态:片段/按前置知识运行
import math as m
print(m.floor(2.9))
# 输出: 2
# 示例类型:关联/组合
组合 2:导入多个名字
python
# 示例目标:导入多个名字
# 前置知识:2.1 函数;第 1 卷 import 基本形式。
# 代码状态:片段/按前置知识运行
from math import floor, ceil
print(floor(2.1), ceil(2.1))
# 输出: 2 3
# 示例类型:关联/组合
易错/易混
易错 1:模块名遮蔽标准库
错误代码:
python
# 示例目标:模块名遮蔽标准库(错误示范)
# 前置知识:2.1 函数;第 1 卷 import 基本形式。
# 代码状态:片段/按前置知识运行
import math
print(math.sqrt(4))
# 示例类型:错误示范
实际结果:如果当前目录有同名 math.py,可能导入错误模块。
修正代码:
python
# 示例目标:模块名遮蔽标准库(修正)
# 前置知识:2.1 函数;第 1 卷 import 基本形式。
# 代码状态:片段/按前置知识运行
import math
print(math.sqrt(4))
# 输出: 2.0
# 示例类型:易错修正
原因:不要用标准库名命名自己的模块;检查导入来源。
易错 2:在导入时执行副作用
错误代码:
python
# 示例目标:在导入时执行副作用(错误示范)
# 前置知识:2.1 函数;第 1 卷 import 基本形式。
# 代码状态:片段/按前置知识运行
def connect():
print("connected")
connect()
# 示例类型:错误示范
实际结果:导入模块就会连接外部服务,测试和 CLI 帮助都会意外执行。
修正代码:
python
# 示例目标:在导入时执行副作用(修正)
# 前置知识:2.1 函数;第 1 卷 import 基本形式。
# 代码状态:片段/按前置知识运行
def connect():
print("connected")
if __name__ == "__main__":
connect()
# 示例类型:易错修正
原因:入口保护能避免导入时执行启动副作用。
练习
- 导入 math 并打印
math.pi的两位小数。 - 写一个带
if __name__ == "__main__":的入口片段。
2.9 函数与模块项目线:TaskFlow v02
前置知识:2.1~2.8。
概念:项目线把第一卷的过程式任务清单拆成函数:添加、完成、筛选和展示各自有明确输入输出,入口只负责组装。这个阶段仍然使用内存数据,不引入持久化。
本单元新语法
| 语法形式 | 含义 |
|---|---|
def add_task(tasks, title) |
添加任务 |
def complete_task(tasks, task_id) |
完成任务 |
def pending(tasks) |
筛选未完成任务 |
match task["priority"] |
按优先级分支 |
if __name__ == "__main__": |
入口保护 |
最小示例
最小示例 1:添加任务函数
python
# 示例目标:添加任务函数
# 前置知识:2.1~2.8。
# 代码状态:完整可运行
def add_task(tasks, title):
task = {"id": str(len(tasks) + 1), "title": title, "status": "pending"}
tasks.append(task)
return task
tasks = []
print(add_task(tasks, "Read"))
# 输出: {'id': '1', 'title': 'Read', 'status': 'pending'}
# 示例类型:最小示例
最小示例 2:筛选函数
python
# 示例目标:筛选函数
# 前置知识:2.1~2.8。
# 代码状态:完整可运行
def pending(tasks):
return [task for task in tasks if task["status"] == "pending"]
print(pending([{"status": "done"}, {"status": "pending"}]))
# 输出: [{'status': 'pending'}]
# 示例类型:最小示例
实际场景示例
这是从脚本到应用的第一层结构:函数有明确职责,入口可被测试导入,下一步再加入异常和持久化。
python
# 示例目标:完整里程碑片段
# 前置知识:2.1~2.8。
# 代码状态:片段/按前置知识运行
def add_task(tasks, title, priority="normal"):
task = {"id": str(len(tasks) + 1), "title": title, "priority": priority, "status": "pending"}
tasks.append(task)
return task
def complete(tasks, task_id):
for task in tasks:
if task["id"] == task_id:
task["status"] = "done"
return task
return None
tasks = []
add_task(tasks, "Read", "high")
print(complete(tasks, "1"))
# 输出: {'id': '1', 'title': 'Read', 'priority': 'high', 'status': 'done'}
# 示例类型:实际场景
关联/组合
组合 1:用推导式筛选高优先级
python
# 示例目标:用推导式筛选高优先级
# 前置知识:2.1~2.8。
# 代码状态:片段/按前置知识运行
tasks = [{"priority": "high"}, {"priority": "low"}]
print([task for task in tasks if task["priority"] == "high"])
# 输出: [{'priority': 'high'}]
# 示例类型:关联/组合
组合 2:用 match 决定下一步
python
# 示例目标:用 match 决定下一步
# 前置知识:2.1~2.8。
# 代码状态:片段/按前置知识运行
def next_step(task):
match task["status"]:
case "pending":
return "start"
case "done":
return "archive"
case _:
return "review"
print(next_step({"status": "pending"}))
# 输出: start
# 示例类型:关联/组合
易错/易混
易错 1:函数修改列表却没有说明
错误代码:
python
# 示例目标:函数修改列表却没有说明(错误示范)
# 前置知识:2.1~2.8。
# 代码状态:片段/按前置知识运行
def add_task(tasks, title):
tasks.append(title)
tasks = []
add_task(tasks, "Read")
print(tasks)
# 示例类型:错误示范
实际结果:输出 'Read';调用方可能没意识到列表被修改。
修正代码:
python
# 示例目标:函数修改列表却没有说明(修正)
# 前置知识:2.1~2.8。
# 代码状态:片段/按前置知识运行
def add_task(tasks, title):
return [*tasks, title]
tasks = []
tasks = add_task(tasks, "Read")
print(tasks)
# 输出: ['Read']
# 示例类型:易错修正
原因:需要值语义时返回新列表;需要原地修改时在命名和文档中说明。
易错 2:完成函数找不到任务仍返回正常值
错误代码:
python
# 示例目标:完成函数找不到任务仍返回正常值(错误示范)
# 前置知识:2.1~2.8。
# 代码状态:片段/按前置知识运行
def complete(tasks, task_id):
for task in tasks:
if task["id"] == task_id:
task["status"] = "done"
return tasks
print(complete([], "missing"))
# 示例类型:错误示范
实际结果:返回空列表,调用方无法知道任务不存在。
修正代码:
python
# 示例目标:完成函数找不到任务仍返回正常值(修正)
# 前置知识:2.1~2.8。
# 代码状态:片段/按前置知识运行
def complete(tasks, task_id):
for task in tasks:
if task["id"] == task_id:
task["status"] = "done"
return task
return None
print(complete([], "missing"))
# 输出: None
# 示例类型:易错修正
原因:失败应返回可识别结果或抛出明确异常,后续卷会引入自定义异常。
练习
- 把
add_task改成返回新列表而不修改输入列表。 - 写一个
find_task(tasks, task_id),找不到返回 None。
2.10 嵌套列表与字典的数据转换
前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
概念:真实数据通常是"列表里放字典、字典里再放列表"。处理时先明确输入形状,再决定过滤、映射、分组、排序和展平顺序;不要在一个推导式里同时完成五件事。
本单元新语法
| 语法形式 | 含义 |
|---|---|
list[dict] |
记录集合 |
dict[str, list] |
分组结果 |
筛选 + 映射 |
先过滤再转换 |
sorted(key=fn) |
按字段排序 |
flatten |
把嵌套列表展开 |
setdefault |
按字段分组 |
最小示例
最小示例 1:筛选任务标题
python
# 示例目标:筛选任务标题
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:完整可运行
tasks = [{"title": "Read", "done": False}, {"title": "Ship", "done": True}]
titles = [task["title"] for task in tasks if not task["done"]]
print(titles)
# 输出: ['Read']
# 示例类型:最小示例
最小示例 2:按字段分组
python
# 示例目标:按字段分组
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:完整可运行
rows = [{"team": "a", "name": "ann"}, {"team": "b", "name": "bob"}, {"team": "a", "name": "amy"}]
groups = {}
for row in rows:
groups.setdefault(row["team"], []).append(row["name"])
print(groups)
# 输出: {'a': ['ann', 'amy'], 'b': ['bob']}
# 示例类型:最小示例
最小示例 3:展平嵌套列表
python
# 示例目标:展平嵌套列表
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:完整可运行
rows = [[1, 2], [3], []]
flat = [value for row in rows for value in row]
print(flat)
# 输出: [1, 2, 3]
# 示例类型:最小示例
实际场景示例
API 列表、报表数据和配置树都使用嵌套结构;转换函数应保持输入不变并返回明确的新结构。
python
# 示例目标:任务看板分组
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:片段/按前置知识运行
tasks = [{"status": "done", "title": "Read"}, {"status": "pending", "title": "Ship"}]
board = {}
for task in tasks:
board.setdefault(task["status"], []).append(task["title"])
print(board)
# 输出: {'done': ['Read'], 'pending': ['Ship']}
# 示例类型:实际场景
关联/组合
组合 1:按标题排序
python
# 示例目标:按标题排序
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:片段/按前置知识运行
tasks = [{"title": "Write"}, {"title": "Read"}]
def title_of(task):
return task["title"]
print(sorted(tasks, key=title_of))
# 输出: [{'title': 'Read'}, {'title': 'Write'}]
# 示例类型:关联/组合
组合 2:提取所有标签
python
# 示例目标:提取所有标签
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:片段/按前置知识运行
tasks = [{"tags": ["work", "urgent"]}, {"tags": ["home"]}]
all_tags = [tag for task in tasks for tag in task["tags"]]
print(all_tags)
# 输出: ['work', 'urgent', 'home']
# 示例类型:关联/组合
组合 3:保留原字典
python
# 示例目标:保留原字典
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:片段/按前置知识运行
task = {"title": "Read", "done": False}
updated = {**task, "done": True}
print(task, updated)
# 输出: {'title': 'Read', 'done': False} {'title': 'Read', 'done': True}
# 示例类型:关联/组合
易错/易混
易错 1:在推导式中做外部 I/O
错误代码:
python
# 示例目标:在推导式中做外部 I/O(错误示范)
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:片段/按前置知识运行
results = [save(task) for task in tasks if task["done"]]
# 示例类型:错误示范
实际结果:每项都会立即执行副作用,异常和事务边界难以控制。
修正代码:
python
# 示例目标:在推导式中做外部 I/O(修正)
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:片段/按前置知识运行
results = []
for task in tasks:
if task["done"]:
results.append(save(task))
# 示例类型:易错修正
原因:纯转换用推导式;有副作用时使用显式循环。
易错 2:忘记嵌套循环顺序
错误代码:
python
# 示例目标:忘记嵌套循环顺序(错误示范)
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:片段/按前置知识运行
rows = [[1, 2], [3]]
flat = [value for value in row for row in rows]
# 示例类型:错误示范
实际结果:变量顺序错误,可能触发 UnboundLocalError 或得到意外结果。
修正代码:
python
# 示例目标:忘记嵌套循环顺序(修正)
# 前置知识:2.1 函数;2.5 推导式;2.7 排序与聚合。
# 代码状态:片段/按前置知识运行
rows = [[1, 2], [3]]
flat = [value for row in rows for value in row]
print(flat)
# 输出: [1, 2, 3]
# 示例类型:易错修正
原因:外层循环写在前面,内层循环写在后面。
练习
- 把任务列表按状态分组并提取标题。
- 把
[[1], [2, 3]]展平并删除重复值。
2.11 函数接口设计与缺失值
前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
概念:好的函数接口要让调用方知道"未提供""值为 None"和"合法假值"的区别。默认参数适合固定值,哨兵对象适合判断是否真正传参;返回值应保持形状稳定。
本单元新语法
| 语法形式 | 含义 |
|---|---|
default value |
调用方省略时使用 |
keyword-only |
强制调用方写参数名 |
sentinel = object() |
唯一缺失标记 |
is sentinel |
判断是否未提供 |
return None |
表示没有结果 |
return tuple |
返回多个相关结果 |
最小示例
最小示例 1:区分 None 与缺失
python
# 示例目标:区分 None 与缺失
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:完整可运行
MISSING = object()
def set_limit(value=MISSING):
if value is MISSING:
return 100
return value
print(set_limit())
print(set_limit(None))
# 输出: 100
# 输出: None
# 示例类型:最小示例
最小示例 2:返回多个值
python
# 示例目标:返回多个值
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:完整可运行
def split_title(text):
parts = text.split(":", 1)
if len(parts) == 1:
return "task", parts[0]
return parts[0], parts[1]
print(split_title("work:Read"))
# 输出: ('work', 'Read')
# 示例类型:最小示例
最小示例 3:仅关键字选项
python
# 示例目标:仅关键字选项
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:完整可运行
def create(title, *, urgent=False):
return {"title": title, "urgent": urgent}
print(create("Read", urgent=True))
# 输出: {'title': 'Read', 'urgent': True}
# 示例类型:最小示例
实际场景示例
配置加载、查询参数和公共库接口经常要区分未提供与 None;哨兵值能避免把 0、空字符串误当成缺失。
python
# 示例目标:可选查询上限
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:片段/按前置知识运行
MISSING = object()
def limit_of(raw=MISSING):
if raw is MISSING:
return 50
if raw is None:
return None
return max(1, min(raw, 500))
print(limit_of())
print(limit_of(None))
# 输出: 50
# 输出: None
# 示例类型:实际场景
关联/组合
组合 1:稳定返回类型
python
# 示例目标:稳定返回类型
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:片段/按前置知识运行
def find_title(tasks, target):
for task in tasks:
if task["title"] == target:
return task
return None
print(find_title([], "Read"))
# 输出: None
# 示例类型:关联/组合
组合 2:参数校验前置
python
# 示例目标:参数校验前置
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:片段/按前置知识运行
def normalize_title(title):
cleaned = title.strip()
if not cleaned:
return None
return cleaned
print(normalize_title(" "))
# 输出: None
# 示例类型:关联/组合
组合 3:返回状态与数据
python
# 示例目标:返回状态与数据
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:片段/按前置知识运行
def parse_int(raw):
if not raw.isdigit():
return False, None
return True, int(raw)
print(parse_int("42"))
# 输出: (True, 42)
# 示例类型:关联/组合
易错/易混
易错 1:用 None 表示所有缺失
错误代码:
python
# 示例目标:用 None 表示所有缺失(错误示范)
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:片段/按前置知识运行
def limit(raw=None):
return 50 if raw is None else raw
print(limit(None))
# 示例类型:错误示范
实际结果:输出 50;调用方无法区分"没有提供"和"明确设为 None"。
修正代码:
python
# 示例目标:用 None 表示所有缺失(修正)
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:片段/按前置知识运行
MISSING = object()
def limit(raw=MISSING):
return 50 if raw is MISSING else raw
print(limit())
print(limit(None))
# 输出: 50
# 输出: None
# 示例类型:易错修正
原因:需要三态语义时使用唯一哨兵。
易错 2:函数返回不同形状
错误代码:
python
# 示例目标:函数返回不同形状(错误示范)
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:片段/按前置知识运行
def result(ok):
if ok:
return [1, 2]
return None
# 示例类型:错误示范
实际结果:调用方必须先判断类型,后续代码容易崩溃。
修正代码:
python
# 示例目标:函数返回不同形状(修正)
# 前置知识:2.2 参数;2.3 作用域;2.10 嵌套数据。
# 代码状态:片段/按前置知识运行
def result(ok):
if ok:
return [1, 2]
return []
print(result(False))
# 输出: []
# 示例类型:易错修正
原因:尽量保持返回值形状稳定。
练习
- 写一个
limit(raw=MISSING),未提供返回 50,明确 None 返回 None。 - 写一个函数,返回
(是否成功, 值)。
2.12 模块拆分、导入边界与 all
前置知识:2.8 模块与入口;2.1 函数。
概念 :模块边界决定谁能依赖谁。业务模块不要导入启动脚本;包的 __init__ 不应执行重副作用;__all__ 控制 from package import * 暴露的名字,也表达公共 API。
本单元新语法
| 语法形式 | 含义 |
|---|---|
import module |
导入模块对象 |
from module import name |
导入单个名字 |
__all__ = [...] |
声明公开导出 |
__name__ |
模块名/入口判断 |
绝对导入 |
从项目包根开始导入 |
循环导入 |
模块互相依赖导致初始化失败 |
最小示例
最小示例 1:公开导出列表
python
# 示例目标:公开导出列表
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:完整可运行
__all__ = ["normalize_title", "create_task"]
print(__all__)
# 输出: ['normalize_title', 'create_task']
# 示例类型:最小示例
最小示例 2:入口判断
python
# 示例目标:入口判断
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:完整可运行
if __name__ == "__main__":
print("run as script")
# 输出: run as script
# 示例类型:最小示例
最小示例 3:从模块导入函数
python
# 示例目标:从模块导入函数
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:完整可运行
from math import sqrt
print(sqrt(16))
# 输出: 4.0
# 示例类型:最小示例
实际场景示例
把解析、业务和入口拆开后,测试可以导入业务模块而不启动服务;公共包还应明确 all。
python
# 示例目标:包的公共接口
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:片段/按前置知识运行
from math import floor
__all__ = ["floor"]
print(__all__)
# 输出: ['floor']
# 示例类型:实际场景
关联/组合
组合 1:模块名与入口分离
python
# 示例目标:模块名与入口分离
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:片段/按前置知识运行
module_name = "taskflow.service"
print(module_name.split(".")[-1])
# 输出: service
# 示例类型:关联/组合
组合 2:避免导入时副作用
python
# 示例目标:避免导入时副作用
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:片段/按前置知识运行
def main():
print("start server")
if __name__ == "__main__":
main()
# 示例类型:关联/组合
组合 3:显式依赖
python
# 示例目标:显式依赖
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:片段/按前置知识运行
from math import sqrt
result = sqrt(25)
print(result)
# 输出: 5.0
# 示例类型:关联/组合
易错/易混
易错 1:用 import * 污染命名空间
错误代码:
python
# 示例目标:用 import * 污染命名空间(错误示范)
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:片段/按前置知识运行
from math import *
print(sqrt(4))
# 示例类型:错误示范
实际结果:虽然能运行,但名字来源不清,覆盖风险高。
修正代码:
python
# 示例目标:用 import * 污染命名空间(修正)
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:片段/按前置知识运行
from math import sqrt
print(sqrt(4))
# 输出: 2.0
# 示例类型:易错修正
原因 :业务代码优先显式导入;all 只控制包的公开导出。
易错 2:在 init 中连接数据库
错误代码:
python
# 示例目标:在 __init__ 中连接数据库(错误示范)
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:片段/按前置知识运行
def connect():
print("connect database")
connect()
# 示例类型:错误示范
实际结果:任何导入包的代码都会触发连接,测试和 CLI 帮助也会受影响。
修正代码:
python
# 示例目标:在 __init__ 中连接数据库(修正)
# 前置知识:2.8 模块与入口;2.1 函数。
# 代码状态:片段/按前置知识运行
def connect():
print("connect database")
if __name__ == "__main__":
connect()
# 示例类型:易错修正
原因:初始化副作用放在明确入口或生命周期函数中。
练习
- 为一个模块声明
__all__,包含两个函数名。 - 解释为什么包的
__init__.py不应执行网络请求。
2.13 分派表与配置驱动编程
前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
概念:当命令/事件类型很多时,if/elif 会越来越长。可以用字典把字符串映射到函数,把"查找"和"执行"分开;这比在业务函数里堆分支更容易扩展和测试。
本单元新语法
| 语法形式 | 含义 |
|---|---|
handlers = {"add": fn} |
命令到函数的映射 |
handlers.get(command) |
安全查找处理器 |
callable(value) |
检查是否可调用 |
for name, fn in handlers.items() |
遍历处理器 |
match |
适合结构化数据分支 |
unknown handler |
统一处理未知命令 |
最小示例
最小示例 1:字典分派
python
# 示例目标:字典分派
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:完整可运行
def add():
return "add"
def show():
return "show"
handlers = {"add": add, "show": show}
print(handlers["add"]())
# 输出: add
# 示例类型:最小示例
最小示例 2:安全查找
python
# 示例目标:安全查找
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:完整可运行
handlers = {"add": str}
command = "delete"
handler = handlers.get(command)
print(handler is None)
# 输出: True
# 示例类型:最小示例
最小示例 3:默认处理器
python
# 示例目标:默认处理器
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:完整可运行
def unknown():
return "unknown"
handlers = {"add": str}
handler = handlers.get("delete", unknown)
print(handler())
# 输出: unknown
# 示例类型:最小示例
实际场景示例
CLI 子命令、Webhook 事件、消息消费者和插件入口都适合分派表;未知命令必须走明确兜底。
python
# 示例目标:任务命令分发
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:片段/按前置知识运行
def add(title):
return "add:" + title
def show(title):
return "show:" + title
handlers = {"add": add, "show": show}
command = {"action": "add", "title": "Read"}
handler = handlers.get(command["action"])
print(handler(command["title"]) if handler else "unknown")
# 输出: add:Read
# 示例类型:实际场景
关联/组合
组合 1:遍历已注册处理器
python
# 示例目标:遍历已注册处理器
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:片段/按前置知识运行
handlers = {"add": str, "show": repr}
for name, handler in handlers.items():
print(name, callable(handler))
# 输出: add True
# 输出: show True
# 示例类型:关联/组合
组合 2:match 处理结构
python
# 示例目标:match 处理结构
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:片段/按前置知识运行
event = {"type": "task", "action": "add"}
match event:
case {"type": "task", "action": action}:
print(action)
case _:
print("unknown")
# 输出: add
# 示例类型:关联/组合
组合 3:分派前的参数校验
python
# 示例目标:分派前的参数校验
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:片段/按前置知识运行
handlers = {"add": str}
title = " "
if title.strip() and "add" in handlers:
print(handlers["add"](title.strip()))
# 输出:
# 示例类型:关联/组合
易错/易混
易错 1:直接调用不存在的处理器
错误代码:
python
# 示例目标:直接调用不存在的处理器(错误示范)
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:片段/按前置知识运行
handlers = {"add": str}
print(handlers["delete"]("x"))
# 示例类型:错误示范
实际结果:抛出 KeyError,用户输入直接变成服务器错误。
修正代码:
python
# 示例目标:直接调用不存在的处理器(修正)
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:片段/按前置知识运行
handlers = {"add": str}
handler = handlers.get("delete")
if handler is None:
print("unknown command")
# 输出: unknown command
# 示例类型:易错修正
原因:未知命令使用 get 和明确兜底。
易错 2:只查找处理器却不调用
错误代码:
python
# 示例目标:只查找处理器却不调用(错误示范)
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:片段/按前置知识运行
def add():
return "add"
handlers = {"add": add}
result = handlers.get("add")
# 示例类型:错误示范
实际结果:result 是函数对象,不是执行结果。
修正代码:
python
# 示例目标:只查找处理器却不调用(修正)
# 前置知识:2.1 函数;2.6 match;2.8 模块;2.11 函数接口。
# 代码状态:片段/按前置知识运行
def add():
return "add"
handler = {"add": add}.get("add")
if handler is not None:
print(handler())
# 输出: add
# 示例类型:易错修正
原因:分派后明确调用,并验证结果/副作用。
练习
- 用字典把
add和show映射到两个命名函数。 - 用
handlers.get为未知命令打印 unknown。
本卷综合实验室:内存任务命令行
实验目标
用函数、参数、返回值、模块入口、推导式和 match 写出一个不依赖外部文件的任务 CLI。它只操作内存数据,重点是接口设计和命令分派。
设计约束
- 任务字段为
id/title/status/priority/tags。 - 添加、完成、筛选、统计分别由函数负责。
- 未知命令必须返回明确错误,而不是 KeyError。
- 所有函数都能被测试单独调用,入口只负责解析演示命令。
完整实现
python
# 示例目标:内存任务 CLI
# 前置知识:第 2 卷函数、参数、推导式、match、模块入口
# 代码状态:完整可运行
MISSING = object()
def add_task(tasks, title, priority="normal", tags=()):
cleaned = title.strip()
if not cleaned:
return None
task = {
"id": str(len(tasks) + 1),
"title": cleaned,
"status": "pending",
"priority": priority,
"tags": tuple(tag.strip().lower() for tag in tags if tag.strip()),
}
tasks.append(task)
return task
def find_task(tasks, task_id):
for task in tasks:
if task["id"] == task_id:
return task
return None
def complete_task(tasks, task_id):
task = find_task(tasks, task_id)
if task is None:
return False
task["status"] = "done"
return True
def pending_titles(tasks):
return [task["title"] for task in tasks if task["status"] == "pending"]
def summarize(tasks):
counts = {"pending": 0, "done": 0}
for task in tasks:
counts[task["status"]] += 1
return counts
def dispatch(tasks, command):
match command:
case {"action": "add", "title": title}:
return add_task(tasks, title)
case {"action": "done", "id": task_id}:
return complete_task(tasks, task_id)
case {"action": "list"}:
return pending_titles(tasks)
case _:
return "unknown command"
tasks = []
commands = [
{"action": "add", "title": " Read "},
{"action": "add", "title": "Ship", "priority": "high"},
{"action": "done", "id": "1"},
{"action": "list"},
]
for command in commands:
result = dispatch(tasks, command)
print("result:", result)
print("summary:", summarize(tasks))
# 输出:
# result: {'id': '1', 'title': 'Read', 'status': 'pending', 'priority': 'normal', 'tags': ()}
# result: {'id': '2', 'title': 'Ship', 'status': 'pending', 'priority': 'high', 'tags': ()}
# result: True
# result: ['Ship']
# summary: {'pending': 1, 'done': 1}
逐段解释
add_task接收容器和字段,返回新任务或None;调用方可以根据返回值判断是否成功。find_task找不到返回None,complete_task把它转换成布尔结果,避免把"找不到"当异常路径。pending_titles使用推导式,保持纯转换,不修改任务。dispatch使用匹配解构命令字典;未知命令返回明确字符串。if __name__在实际脚本中用于入口保护;本实验直接调用以便观察结果。
本卷关联
- 参数默认值和 tuple 标签来自 2.2。
- 推导式来自 2.5,匹配来自 2.6。
- 模块入口来自 2.8,函数接口设计来自 2.11。
- 分派表思想来自 2.13。
验收标准
- 空标题不会添加任务。
- 完成任务后
summary的 pending/done 正确变化。 - 未知命令不会抛出 KeyError。
- 函数返回形状稳定,调用方不需要猜测类型。
复习问答
问:为什么 add_task 既修改列表又返回任务?
答:这是实验中的显式双语义;真实 API 应二选一:原地修改就返回任务/状态,值语义就返回新列表,不能悄悄两者都做。
问:为什么不在这里引入类?
答:本卷目标是函数和模块边界;类和类型系统在下一卷引入,提前使用会破坏线性学习。
本卷练习答案
先独立完成练习,再展开答案;答案只使用本卷及之前介绍过的知识。
2.1 函数定义、调用与返回值 练习答案
练习 1 :定义 square(n) 返回平方。
python
def square(n):
return n * n
print(square(4))
练习 2 :定义 label(score),60 分及以上返回 pass,否则返回 fail。
python
def label(score):
return "pass" if score >= 60 else "fail"
print(label(60))
2.2 参数形态与调用约定 练习答案
练习 1 :给 create(title, priority="normal") 增加仅关键字参数 tags。
python
def create(title, *, priority="normal", tags=None):
return title, priority, tags or []
print(create("Read", tags=["study"]))
练习 2 :用 **options 调用一个接收 title 和 done 的函数。
python
def show(title, done=False):
print(title, done)
show(**{"title": "Read", "done": True})
2.3 作用域、闭包与迟绑定 练习答案
练习 1:写一个闭包计数器,每次调用加一并返回。
python
def make_counter():
count = 0
def step():
nonlocal count
count += 1
return count
return step
step = make_counter()
print(step(), step())
练习 2 :说明为什么 for 中创建的闭包可能都返回最后一个值。
python
funcs = []
for i in range(2):
def show():
return i
funcs.append(show)
values = []
for fn in funcs:
values.append(fn())
print(values)
2.4 解包、星号表达式与函数调用展开 练习答案
练习 1 :用 * 合并两个列表。
python
print([*[1, 2], *[3]])
练习 2 :把字典 options 展开传给函数 show(title, done=False)。
python
def show(title, done=False):
print(title, done)
show(**{"title": "Read", "done": True})
2.5 列表、字典与集合推导式 练习答案
练习 1:用列表推导式取出 1 到 10 中的偶数。
python
print([n for n in range(1, 11) if n % 2 == 0])
练习 2 :用字典推导式建立 id -> title 映射。
python
tasks = [{"id": "a", "title": "Read"}]
print({t["id"]: t["title"] for t in tasks})
2.6 结构化模式匹配 match/case 练习答案
练习 1:用 match 判断列表是否为空并打印 empty/non-empty。
python
items = []
match items:
case []:
print("empty")
case _:
print("non-empty")
练习 2 :匹配字典 {"type": "user", "name": "ann"} 并提取 name。
python
event = {"type": "user", "name": "ann"}
match event:
case {"type": "user", "name": name}:
print(name)
2.7 enumerate、zip、sorted 与聚合函数 练习答案
练习 1:用 enumerate 打印带编号的任务。
python
for i, task in enumerate(["a", "b"], 1):
print(i, task)
练习 2:判断列表是否至少有一个负数。
python
print(any(n < 0 for n in [1, -2, 3]))
2.8 模块、导入与脚本入口 练习答案
练习 1 :导入 math 并打印 math.pi 的两位小数。
python
import math
print(f"{math.pi:.2f}")
练习 2 :写一个带 if __name__ == "__main__": 的入口片段。
python
if __name__ == "__main__":
print("run")
2.9 函数与模块项目线:TaskFlow v02 练习答案
练习 1 :把 add_task 改成返回新列表而不修改输入列表。
python
def add_task(tasks, title):
return [*tasks, title]
print(add_task([], "Read"))
练习 2 :写一个 find_task(tasks, task_id),找不到返回 None。
python
def find_task(tasks, task_id):
for task in tasks:
if task["id"] == task_id:
return task
return None
print(find_task([], "x"))
2.10 嵌套列表与字典的数据转换 练习答案
练习 1:把任务列表按状态分组并提取标题。
python
tasks = [{"status": "done", "title": "A"}, {"status": "open", "title": "B"}]
groups = {}
for task in tasks:
groups.setdefault(task["status"], []).append(task["title"])
print(groups)
练习 2 :把 [[1], [2, 3]] 展平并删除重复值。
python
rows = [[1], [2, 3], [1]]
values = []
for row in rows:
for value in row:
if value not in values:
values.append(value)
print(values)
2.11 函数接口设计与缺失值 练习答案
练习 1 :写一个 limit(raw=MISSING),未提供返回 50,明确 None 返回 None。
python
MISSING = object()
def limit(raw=MISSING):
if raw is MISSING:
return 50
return raw
print(limit(), limit(None))
练习 2 :写一个函数,返回 (是否成功, 值)。
python
def parse(raw):
if raw.isdigit():
return True, int(raw)
return False, None
print(parse("3"))
2.12 模块拆分、导入边界与 all 练习答案
练习 1 :为一个模块声明 __all__,包含两个函数名。
python
__all__ = ["create", "list_items"]
print(__all__)
练习 2 :解释为什么包的 __init__.py 不应执行网络请求。
python
print("import must stay lightweight")
2.13 分派表与配置驱动编程 练习答案
练习 1 :用字典把 add 和 show 映射到两个命名函数。
python
def add():
return "add"
def show():
return "show"
handlers = {"add": add, "show": show}
print(handlers["show"]())
练习 2 :用 handlers.get 为未知命令打印 unknown。
python
handlers = {"add": str}
handler = handlers.get("x")
print("unknown" if handler is None else handler("x"))
本卷验收
- 能解释本卷每个"本单元新语法"表中的形式。
- 能独立完成全部练习,并说明错误示例失败的原因。
- 能运行项目里程碑:
taskflow/milestones/v02_functional_tasks.py --demo。 - 能说出下一卷开始前必须掌握的三个概念。