习题 — 第 2 章:函数
一组练习题,按它们所练习的小节分组。请自己动手;凡题目中给出代码,都当作给定数据或起点。每题都附示例输出,让你知道目标是什么。本页不提供解答。
1. 定义与调用函数
练习 1.2 — 一个没有形参的函数
写 greet(),不取任何实参,返回 "Hello, class!"。调用它。然后打印 greet(不带括号),并解释其中的差别。
示例输出
练习 1.3 — return vs print
写两个函数:add_return(a, b) 返回 a + b,add_print(a, b) 把它打印出来。把每次调用的结果存进一个变量并打印那个变量。为什么其中一个是 None?
示例输出
练习 1.6 — 被共享的可变默认值
先预测输出,再运行。解释是默认列表住在哪里,让第二次和第三次调用越长越大。
def append_to(element, to=[]):
to.append(element)
return to
print(append_to(1))
print(append_to(2))
print(append_to(3))
示例输出
练习 1.7 — 修好这个陷阱
用 None 哨兵的写法重写 append_to,让每次不带列表的调用都从头开始。确认 append_to(1) 和 append_to(2) 各返回一个单元素列表。
示例输出
练习 1.8 — 局部是私有的
运行下面的代码。为什么最后一行抛出 NameError?
示例输出
2. 命名空间与作用域
练习 2.1 — 预测 LEGB
先预测全部三行输出,再运行。
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x)
inner()
print(x)
outer()
print(x)
示例输出
练习 2.4 — UnboundLocalError
解释这为什么会报错,并用两种方式修好它:一次用 global,一次把 counter 传进来、再返回新值。
示例输出
练习 2.6 — nonlocal
写 outer(),定义 message = "before",再写一个嵌套的 inner(),用 nonlocal 把它设为 "after"。调用 inner(),再从 outer 里打印 message。
示例输出
练习 2.7 — 为什么一个交换函数会失败
运行这段代码。为什么之后 a 和 b 没变?用局部名称来讨论它。
示例输出
练习 2.8 — 查看命名空间
写一个有一个局部变量的函数,打印 list(locals()),以及名字 "print" 是否在 dir(__builtins__) 里。确认那个局部出现了、而 print 是个内置。
示例输出
3. 一等与高阶函数
练习 3.3 — 用 key 排序
给定 words = ["python", "is", "great"],用带 key 的 sorted 按长度把它们排序。
示例输出
练习 3.4 — 位置 vs 关键字
定义 rect(width, height),返回面积。先按位置调用一次,再用两个关键字、以相反顺序调用一次;确认结果相同。然后解释为什么 rect(height=3, 4) 是个错误。
示例输出
练习 3.6 — **kwargs
写 scoreboard(**players),接受像 scoreboard(Alice=9, Bob=7) 这样的调用,并为每一项打印 "Alice: 9"。(回忆 kwargs 是一个字典。)
示例输出
练习 3.7 — 解包谜题
预测每个名称被绑定到的值。
示例输出
练习 3.11 — 带私有状态的闭包
写 make_account(balance),返回一个含两个函数的字典:deposit(amount) 和 withdraw(amount),它们共享一个私有的 balance(外面无法直接访问它)。从起始余额 100 存入 50、取出 30 后,一个 balance() 报告器应当显示 120。
示例输出
练习 3.12 — lambda
给定 people = [("Ada", 36), ("Bob", 41), ("Cleo", 29)],用一个 lambda key 配合 max 找出最年长者,并按名字排序。
示例输出
4. 实践用例
装饰器
练习 4.1 — 一个记日志的装饰器
写一个装饰器 announce,使装饰 add(a, b) 后调用 add(2, 3) 时,会在前后各打印一行,然后返回结果。
示例输出
练习 4.2 — 一个计时装饰器
写一个装饰器 timed,打印被包装函数花了多长时间(前后各用一次 time.perf_counter())。把它用到一个对 range(1_000_000) 求和的函数上。
示例输出
练习 4.4 — assert 作为守卫
写 mean(values),用 assert 拒绝空列表和含 None 的列表,否则返回平均值。
示例输出
递归
练习 4.7 — 斐波那契
写一个递归的 fib(n),fib(0) = 0、fib(1) = 1,并打印前十个斐波那契数。
示例输出
map、filter、reduce
生成器
练习 4.13 — 一个偶数生成器
写一个生成器 evens(limit),产出 0、2、4、… 直到(不含)limit,并打印 list(evens(10))。
示例输出
练习 4.15 — 生成器表达式
用一个生成器表达式,构建一个 0–4 立方的生成器,打印它的 type,再打印它的 list(...)。
示例输出
练习 4.16 — 一次性
生成器是一次性的。预测下面第二次 list(...) 并解释。
示例输出
错误处理
练习 4.17 — 安全除法
写 safe_divide(a, b),返回 a / b,或在除以零时打印一条消息并返回 None。
示例输出
练习 4.19 — try / except / else / finally
写 read_int(text),成功时返回 int(text)、遇 ValueError 时返回 None,在 else 里打印 "ok"、在 finally 里打印 "done"。对 "42" 和 "oops" 各调用一次。
示例输出
5. 补遗与风格
练习 5.2 — 仅关键字形参
定义 make_user(name, *, admin=False)。展示 make_user("Ada", True) 抛出 TypeError、而 make_user("Ada", admin=True) 能用。解释为什么。
示例输出