← 返回 Day 12 正文

Day 12 · 练习与清单

Day 012 — 函数进阶:练习题与完成清单

完成所有练习后,在下方勾选 ✓,巩固今日所学。


📋 完成清单


💪 练习题

练习 1:修复默认参数陷阱

下面的函数有一个"陷阱",请找出并修复它。同时解释为什么它是错的。

python
def register_students(student_names, enrolled=[]):
    """注册学生到课程"""
    for name in student_names:
        enrolled.append(name)
    return enrolled

# 测试
print(register_students(["张三", "李四"]))
print(register_students(["王五"]))  # 预期: ["王五"], 实际呢?

要求: 1. 先用原始代码跑一遍,观察问题 2. 使用 __defaults__ 或 id() 验证问题原因 3. 修复代码(使用 None 模式) 4. 验证修复后每次调用都能得到正确结果


练习 2:迷你计算器 (args/kwargs)

写一个函数 calculator(operation, *numbers, **options),支持:

示例行为:

python
calculator("add", 1, 2, 3, 4)           # 返回 10
calculator("multiply", 2, 3, 4)          # 返回 24
calculator("power", 2, 3, 4)             # 返回 4096 (2^3^4)
calculator("add", 1.234, 5.678, precision=1)  # 返回 6.9
calculator("add", 1, 2, 3, show_steps=True)   
# 打印: 1 + 2 = 3, 3 + 3 = 6, 6 + 4 = 10

提示: - power 操作需要用循环:从第一个数开始,反复对下一个数做幂运算 - show_steps=True 时打印中间结果 - 使用 **options.get("precision", 2) 获取精度设置


练习 3:带 Type Hints 的字符串处理函数

编写一个函数 extract_emails(text: str) -> list[str],从一段文本中提取所有电子邮件地址。

要求: - 完整的 Type Hints(参数和返回值) - 使用 @ 符号作为简单检测指标 - 返回找到的所有邮箱地址(去重) - 如果没有找到,返回空列表 []

示例:

python
text = "联系我们: support@example.com 或 admin@test.org, 也可以发邮件给 info@demo.com"
print(extract_emails(text))
# ['support@example.com', 'admin@test.org', 'info@demo.com']

text2 = "这里没有邮箱"
print(extract_emails(text2))
# []

进阶挑战: 给函数添加一个可选参数 domain_filter: str | None = None,只返回指定域名的邮箱:

python
text = "联系: a@gmail.com 或 b@outlook.com 或 c@gmail.com"
print(extract_emails(text, domain_filter="gmail.com"))
# ['a@gmail.com', 'c@gmail.com']

练习 4:通用日志格式化器

利用 *args 和 **kwargs 写一个灵活的日志函数 log_message:

python
def log_message(level, *messages, **options):
    """
    level: 日志级别 (INFO, WARN, ERROR)
    *messages: 任意数量的消息
    **options: 可选的格式选项
        - timestamp (bool): 是否显示时间戳
        - separator (str): 多个消息之间的分隔符
        - output (str): "console" 或 "file"
    """
    # 你的代码在这里

要求: 1. 用 [] 包裹日志级别,如 [INFO], [WARN] 2. 如果有时间戳,格式为 [2026-06-04 18:30:00] 3. 多个消息之间用 separator 分隔(默认是空格) 4. 如果 output="file",除了打印还要返回格式化后的字符串

示例行为:

python
log_message("INFO", "系统启动", "加载配置", timestamp=True)
# 输出: [2026-06-04 18:30:00] [INFO] 系统启动 加载配置

log_message("ERROR", "连接失败", "重试中", "放弃", separator=" → ")
# 输出: [ERROR] 连接失败 → 重试中 → 放弃

log = log_message("WARN", "磁盘不足", output="file")
print(log)  # 同时打印并返回字符串

练习 5:综合 — 灵活的报告生成器

写一个函数 generate_report(title, *data_points, agg_func=None, **options),综合利用今天的所有知识点。

规格:

参数 类型 说明
title str 报告标题
*data_points int \| float 数据点
agg_func Callable \| None 自定义聚合函数;None 时使用内置聚合
**options — 见下表

options 支持:

选项 类型 默认值 说明
builtin_method str "sum" 内置聚合方法(sum/mean/max/min)
sort_data bool False 是否排序数据
show_distribution bool False 是否显示分布(计算最大值-最小值)
precision int 2 结果精度

示例:

python
# 基本使用
print(generate_report("销售数据", 100, 200, 150, 300, 250))
# 输出格式: 报告 [销售数据]: sum = 1000.00

# 自定义聚合
print(generate_report("测试", 1, 2, 3, agg_func=lambda x: sum(x) / len(x)))

# 完整配置
print(generate_report("学生成绩", 85, 92, 78, 95, 88,
                      sort_data=True,
                      show_distribution=True,
                      precision=1,
                      builtin_method="mean"))

提示: - 如果 agg_func 不为 None,优先使用自定义聚合函数 - 自定义函数接收一个列表参数,返回一个值 - 使用 isinstance(agg_func, Callable) 来判断


📝 挑战题(选做)

挑战:默认可变参数的"合法"利用

虽然默认参数陷阱通常被认为是 bug,但有一种技术叫 memoization(记忆化/缓存) 有意识地利用了这个特性。请实现一个带缓存的斐波那契数列计算器:

python
def fibonacci(n, cache={}):
    """计算第 n 个斐波那契数,利用默认参数缓存中间结果"""
    # 你的代码

要求: 1. 不使用类、不使用全局变量 2. 利用默认参数陷阱中的 cache 字典自动缓存计算结果 3. 计算 fibonacci(50) 应该瞬间完成(而不是数分钟) 4. 解释为什么这种方法"有效"

延伸思考: - 这个方法有什么缺点? - 你会用什么样的"更干净"的方式实现同样的缓存功能? - 如果用户意外传入一个 cache 参数会怎样?


🔍 课后自查

学完今天的内容后,检查自己是否能回答以下问题:


提示: 所有练习的答案不在仓库中,建议你在实际运行代码中探索和学习。遇到不懂的可以: 1. 在代码中加 print() 打印中间结果 2. 使用 type() 和 id() 观察对象 3. 在 Python 交互式环境(REPL)中实验