第 2 卷:函数、模块与推导式

第 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 立即结束函数;需要后续逻辑时先处理失败分支。

练习

  1. 定义 square(n) 返回平方。
  2. 定义 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

# 示例类型:易错修正

原因:关键字名必须与形参名一致,拼写错误不会被自动修正。

练习

  1. 给 create(title, priority="normal") 增加仅关键字参数 tags。
  2. 用 **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]

# 示例类型:易错修正

原因:用默认参数固定当前值,避免迟绑定。

练习

  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']

# 示例类型:易错修正

原因:星号会展开可迭代对象的元素。

练习

  1. 用 * 合并两个列表。
  2. 把字典 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. 用列表推导式取出 1 到 10 中的偶数。
  2. 用字典推导式建立 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

# 示例类型:易错修正

原因:外部输入的分发函数应有明确兜底。

练习

  1. 用 match 判断列表是否为空并打印 empty/non-empty。
  2. 匹配字典 {"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。

练习

  1. 用 enumerate 打印带编号的任务。
  2. 判断列表是否至少有一个负数。

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()

# 示例类型:易错修正

原因:入口保护能避免导入时执行启动副作用。

练习

  1. 导入 math 并打印 math.pi 的两位小数。
  2. 写一个带 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

# 示例类型:易错修正

原因:失败应返回可识别结果或抛出明确异常,后续卷会引入自定义异常。

练习

  1. 把 add_task 改成返回新列表而不修改输入列表。
  2. 写一个 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. 把 [[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))
# 输出: []

# 示例类型:易错修正

原因:尽量保持返回值形状稳定。

练习

  1. 写一个 limit(raw=MISSING),未提供返回 50,明确 None 返回 None。
  2. 写一个函数,返回 (是否成功, 值)。

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()

# 示例类型:易错修正

原因:初始化副作用放在明确入口或生命周期函数中。

练习

  1. 为一个模块声明 __all__,包含两个函数名。
  2. 解释为什么包的 __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

# 示例类型:易错修正

原因:分派后明确调用,并验证结果/副作用。

练习

  1. 用字典把 add 和 show 映射到两个命名函数。
  2. 用 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}

逐段解释

  1. add_task 接收容器和字段,返回新任务或 None;调用方可以根据返回值判断是否成功。
  2. find_task 找不到返回 None,complete_task 把它转换成布尔结果,避免把"找不到"当异常路径。
  3. pending_titles 使用推导式,保持纯转换,不修改任务。
  4. dispatch 使用匹配解构命令字典;未知命令返回明确字符串。
  5. 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。
  • 能说出下一卷开始前必须掌握的三个概念。
相关推荐
总裁余(余登武)1 小时前
K-means算法改进(Python版)
python·算法·kmeans
Shockang1 小时前
项目驱动的 Python 入门
python
余槐i2 小时前
transformers 升级后加载旧权重报错:三处不兼容如何逐一验证
python·大模型·transformers·模型量化·模型推理
CCblandy2 小时前
向量检索 ANN 参数实测:换一种数据分布,recall 掉了 51 个百分点
人工智能·python·算法·自然语言处理·性能优化
云和数据.ChenGuang3 小时前
print(f“a+b=“,result) print(f“a+b={result}“)区别
java·前端·人工智能·python·深度学习
2601_962885723 小时前
如何用 Python 算最大回撤和回撤恢复期?
java·数据库·python
Want5953 小时前
Python大雪纷飞①
开发语言·python·数学建模
程序员清风3 小时前
Python 数据可视化入门:Matplotlib 核心用法
python·信息可视化·matplotlib
溪语流沙3 小时前
【Web全栈进阶】前端对接新契约:信封解包 + 统一错误处理
前端·vue.js·python·状态模式·fastapi