函数 — 给代码块起名字,随叫随到
函数 — 给代码块起名字,随叫随到
这一章是全书的中轴。 前面八章学的每样东西——打印、运算、分支、循环—— 都会被封进一个叫「函数」的盒子里,起个名字,随叫随到。 原书用烤面包机打比方:你不用懂烤面包机的内部构造,塞进去面包(输入), 弹出来吐司(输出)。函数就是这个盒子,这一章教你造盒子。
1. 这一章在全书链条里的位置
第 02 章你第一次调用函数(print()、input()),用了七章别人的盒子;
这一章你自己造盒子。为什么现在才造?因为盒子里要装的货——循环、分支——
刚刚齐备。函数 = 命名 + 输入口 + 输出口 + 一段已经学会的控制流(代码执行的走向:顺序、分支、循环),
它是前面一切知识的封装层,也是后面一切大程序的组织方式。
2. 顶层全景:一个函数的四格档案
先给一个本章的读法建议:这一章的概念其实只有三个——定义(def)、 传参(参数与实参)、返回(return)——后面的默认参数、关键字传参、 多值返回,全是这三件事的变体。读的时候每遇新概念,问一句「它是定义、 传参还是返回的变体」,章节地图会瞬间变平。
名字 def say_hello(): —— 盒子的标签
输入口 def greet(name): —— 参数(可零个、一个、多个,可带默认值)
身体 缩进的语句块 —— 干活的部分(打印 / 循环 / 分支)
输出口 return 值 —— 交还结果(可省略;省略则交还 None)
图说:四格里只有「身体」是必须的;名字、输入口、输出口都允许空着。3. 核心原理一:函数是什么、为什么值得造
原书给的定义:函数是可复用代码的建筑块——把相关代码收进逻辑单元, 程序因此更有组织、更可读、更好维护1。三个用途逐条拆开2:
- 写一次,用多次——同一段代码不必复制粘贴 N 份;
- 切块——大程序切成小函数,好维护、好调试;
- 命名逻辑段——别人(和三个月后的你)读程序时,函数名就是目录。
烤面包机的比方接着说:函数是黑箱——你只关心它的接口(输入什么、
输出什么),不关心内部构造3。这也是 Python 社区的运转方式:
每个人贡献函数,每个人受益3。书里还给了个立刻能用的自查工具:
help(函数名) 会打印官方说明书——help(print) 的输出里藏着
print 的全部参数(sep、end 等,第 02 章埋的伏笔在这里兑现)4。
练习里的一个提法值得记住:用 IPO(input → process → output)
描述任何函数——input(prompt) 是「提示语 → 等待输入 → 字符串」,
range(2, 10, 3) 是「起终步 → 造数列 → range 对象」5。
能给一个函数说出 IPO,你就掌握了它。
4. 核心原理二:定义函数 — def、缩进、pass
最小的函数长这样6:
def say_hello():
print('Hello!')
say_hello() # 调用:输出 Hello!拆解:def 是关键字(declare function);say_hello 是函数名;
圆括号里现在没参数;冒号收尾;缩进的语句是函数身体——缩进规则
和 if、while 完全一样(第 07 章的缩进即语法,在这里原样适用)6。
定义 ≠ 执行。def 那一行只登记了函数,真正干活的是后面的
say_hello() 调用——圆括号是「执行它」的开关。只写 say_hello
不写括号,函数不会跑(它只是个值)。
两个变体顺手收进工具箱:say_goodbye() 的函数体里装了一个 for 循环
(函数不限于几行,循环分支随便装)7;empty() 的函数体只有一个
pass——pass 是「合法的占位符」,表示「这里故意留白,以后填」,
写框架时专用8。
5. 核心原理三:参数与实参 — 槽位与填料
这一章的术语区分必须咬死,原书专门立了一节9:
- 参数(parameter):定义时的槽位名——
def greet(name)里的name; - 实参(argument):调用时的实际填料——
greet('Alice')里的'Alice'。
主线例子:greet('Alice') 的逐步回放
原书把调用过程拆成了四帧动画,这是理解函数执行的最重要一步10:
def greet(name):
print(f'Hello, {name}!')
greet('Alice') 第 1 帧 遇到 greet('Alice'),解释器去找 greet 的定义
第 2 帧 找到 def greet(name),跳过去
第 3 帧 实参对号入座:name = 'Alice'(参数被赋值为实参)
第 4 帧 执行函数体:打印 Hello, Alice!
第 5 帧 函数体跑完,跳回调用处,继续往下走
图说:调用 = 跳过去 → 对号入座 → 干活 → 跳回来。
「name = 'Alice'」这一步就是书里说的「按值传递」(passing by value):
传进去的是值本身,参数是接住它的新变量。按值传递的实证:double(a)
def double(x):
x = x * 2
print(f'x is {x}.')
a = 5
double(a)
print(f'a is {a}') # 输出 x is 10. 然后 a is 5函数里 x 翻倍成 10,外面的 a 纹丝不动还是 511。为什么?
第 3 帧已经给了答案:x = a 是一次赋值——x 是函数自己的局部变量,
x = x * 2 改的是 x 的指向,a 的指向没人碰。这正是第 03 章
「变量是引用、赋值换指向」模型在函数门口的复述:函数参数的接收,
本质就是赋值语句11。
练习区这一节的密度是全章最高的:hello_name、hello_world_n(n)、
print_prime_2_n(n)、even(n)、abs(n)、square(n)、prime(n)……
清一色「一个参数 + 打印结果」的范式12。注意一个细节:abs(n)
在练习里先以打印版出现(9.4.3),到 9.9.2 又以 return 版出现——
同一个功能、两种交付方式,正好引出下一节的主题。
6. 核心原理四:多参数 — 槽位按序对号
两个参数就是两个槽位:def rectangle_area(length, width): return length * width,
调用 rectangle_area(4.5, 6) 得 27.0——sum(5, 10) 得 15、
average(20, 30) 得 25.0,同一骨架13。
练习里最有营养的是 print_n_to_m_challenge(n, m):升级版要求
不要求 n < m——正着走用 range(n, m+1),倒着走用 range(n, m-1, -1)
(步长 -1,range 倒行)14。这是第 08 章 range 步长知识的第一次实战回扣。
min(x, y, z) 三参数版则演示了「擂台法」求最值:设擂主,逐个挑战15。
7. 核心原理五:两种传参方式 — 位置与关键字
多个参数,两种喂法16:
def greet(name, age):
print(f'Hello, {name}! You are {age} years old.')
greet('Neo', 18) # 按位置:顺序即契约,输出正常
greet(18, 'Neo') # 按位置但顺序反了 → Hello, 18! You are Neo years old.
greet(age=18, name='Neo') # 按关键字:名字即契约,顺序随便,输出正常按位置传:省事,但顺序错则全盘错(18 和 'Neo' 互换,程序不报错,
输出 nonsense——哑错最危险)16。按关键字传(参数名=值):啰嗦一点,
但每个值的去处一目了然,顺序随意;代价是必须记住参数名16。
原书给的裁决值得照抄:参数少、顺序显而易见 → 按位置;参数多、 可选参数多 → 按关键字——「数据科学场景里后者更常见」17。
8. 核心原理六:默认参数 — 让槽位可省略
def greet(name, msg='Hi'):
print(f'{msg}, {name}!')
greet('Neo') # msg 用默认值:输出 Hi, Neo!
greet('Neo', 'Hello') # msg 被覆盖:输出 Hello, Neo!定义时给参数赋值(msg='Hi'),调用时不传就用默认值18。
一条硬规矩:带默认值的参数必须排在不带默认值的后面——
def greet(msg='Hi', name) 直接语法错误,因为按位置传参时会
产生「到底谁填谁」的歧义19。
默认参数的两个实战联想(书里点到为止,我们补全):一是 print()
和 range() 现在看懂了一半——print 的 sep=' '、end='\n'
正是默认参数,第 02 章「逗号补空格」的谜底在这里揭开20;
二是默认参数让「可选定制」变得自然:print_numbers(end, start=0, step=1)
三个参数两个带默认,调用时只喂 print_numbers(5) 或
print_numbers(start=2, end=5, step=2) 都行21。
welcome(start_time='17:30', verb='start') 练习是本章最综合的题:
默认值 + while True 循环 + 字符串切片取「hh:mm」的小时分钟
(start_time[:2] 和 start_time[-2:])+ 转换成分钟数比较——
第 05 章切片、第 03 章类型转换、第 08 章循环全在一段函数里会师22。
输出示例:17:05 时提示「还有 25 分钟开课」,到点(left < 1)break 收工。
9. 核心原理七:return — 函数如何交还结果
打印版函数和 return 版函数的分水岭,是全书后半程最重要的概念升级:
def greet_print(name): # 打印版:结果打到屏幕上,就没了
print(f'Hello, {name}!')
def greet_return(name): # return 版:结果交还给调用处,想怎么用怎么用
return f'Hello, {name}!'
message = greet_return('Neo') # 返回值存进变量
print(greet_return('Neo')) # 或当场打印:Hello, Neo!原书对 return 的定位:函数跑完后,可以把一个值交还给调用方,
调用方拿着它能存变量、能喂别的函数、能进条件判断23。
Jupyter 里单独跑 greet_return('Neo'),单元格下方回显
{"type":"string"}——那就是「交还了一个字符串」的可视证据24。
对比 greet_print('Neo') 在 Jupyter 里的表现:输出出现在流的正文里,
单元格下方没有回显——打印是「效果」,return 是「值」,两种存在方式
在 Jupyter 的界面里看得一清二楚。
四种返回类型,各配一个标准范例25:
| 返回类型 | 例子 | 代码 |
|---|---|---|
| int / float | give_me_5()、average(1,2,3) | return 5 / return (x+y+z)/3 |
| str | welcome('Neo') | return f'Welcome, {name}!' |
| bool | even(n)、prime(n) | return True / return n > 0 |
| 多个值 | get_profile() | return first, last, age, zip |
布尔返回的两个写法值得对比:even(n) 用 if-else 明写 True/False;
positive(n) 直接 return n > 0——比较运算的产出本来就是布尔,
多写 if 是绕路26。prime(n) 的 return 版则展示了「提前交卷」:
发现因数立刻 return False,循环全跑完还没交卷才 return True27——
和素数案例的 break 同构,只是这次用 return 直接离开函数。
多值返回是全章压轴:return first, last, age, zip 一次交出四件,
调用处收到的其实是一个元组(tuple)——'('Thomas', 'Anderson', 18, '09090')'
那对圆括号就是证据28。这正是第 12 章元组的入场券:
stats(1, 2, 3) 返回 (1, 3, 2.0, 6),min、max、均值、总和一件不少29。
挑战版 stats() 更进一步:循环里问 n 个数、擂台法追踪最值、
f-string 打报告、最后 return 四元组——输入 5 个数(1.1, 2.2, 3.3, -9, 4.4)
输出「min -9.00 / max 4.40 / mean 0.40 / total 2.00」,返回
(-9.0, 4.4, 0.4, 2.0),八章知识全部在场的毕业设计30。
10. 打印版 vs return 版:同一个功能的两种交付
本章最值钱的一组对照,是 abs / even / prime 三个功能各写两遍的编排。 把两版并排放,差别一目了然1225:
# abs 打印版(9.4.3):结果打在屏幕上
def abs(n):
if n < 0:
n = -n
print(n)
# abs return 版(9.9.2):结果交还给调用处
def abs(n):
if n < 0:
n = -n
return n# prime 打印版:两种情况各打一句话
def prime(n):
prime = True
for i in range(2, n):
if n % i == 0:
prime = False
break
if prime:
print(n, 'is prime')
else:
print(n, 'is not prime')
# prime return 版:只回答 True/False,话术交给调用者
def prime(n):
for i in range(2, n):
if n % i == 0:
return False
return True三处系统性差别:
| 打印版 | return 版 | |
|---|---|---|
| 结果去哪 | 屏幕上,看完就没 | 调用处,可存变量、可再加工 |
| 话术在哪 | 焊死在函数里(中英、措辞改不了) | 函数只给事实,话术调用方定 |
| 可组合性 | 差:没法把打印版嵌进算式 | 强:prime(x) and x > 100 随便嵌 |
判断(我们的,不是书里的): 默认写 return 版,打印是调用方的自由。 打印版把「算」和「秀」焊死在一起,换个输出场景(写进文件、发上网络) 就得重写函数;return 版只负责「算」,「秀」的方式想换就换。 书里的编排让打印版先出现、return 版后出现,顺序本身就是答案: 打印版是学步车,return 版才是自行车。 如果错,会错在: 脚本式的一次性小工具里,打印版反而更省事—— 教学场景之外,两种风格在真实代码里都有大量存在,判据是「这个结果 还有没有下文」。
11. 练习走查:参数化练习族的三个梯度
本章练习题量全书最大(四十余道),但细看是一条精心设计的梯度, 分三档1215:
第一档:固定行为 → 参数化(参数从无到有)。 hello_world() 打印一次
→ hello_world_n(n) 打印 n 次;print1to10() 打印 1–10 →
print_int_1_n(n) 打印 1–n。改写动作只有一步:把写死的数字换成参数名。
这一档教你「参数 = 可替换的螺丝孔」。
第二档:参数参与判断(参数进分支)。 even(n)、abs(n)、
prime(n)、divisible_by_6(n)——参数先被 % 试探,再进 if-else
决定输出。第 07 章的分支知识在这里全部复用,只是判断对象从 input
换成了参数12。
第三档:参数驱动循环(参数进 range)。 print_n_to_m(n, m)、
sum_n_to_m(n, m)、print_prime_2_n(n)——参数直接喂给 range 的
起点和终点,循环的圈数由调用者定。sum_n_to_m(2, 5) 内部是
range(2, 6) 累加得 14;sum_n(100) 累加 1 到 100 得 505015。
这一档教你「参数 = 循环的规模」。
走完三档回头看,参数的三个作用域齐了:可以被打印、可以被判断、 可以驱动循环——函数的全部威力,就是这三个作用域的组合。
12. 函数命名与调用嵌套:书里的暗线
两个正文没讲、但练习里反复出现的惯例,补在这里:
命名惯例:全小写加下划线。 say_hello、rectangle_area、
sum_of_squares——函数名沿用第 03 章变量的命名惯例(PEP 8 同款),
且名字最好是动词或动宾短语(函数是动作,变量是事物):
greet 好,greeting 是名词;print_prime_2_n 里 print 开头
明示了它是打印版12。
调用可以嵌套,实参可以是任何表达式。 原书 print(abs(-1))
就是嵌套:先算 abs(-1) 得 1,再把这个 1 喂给 print25。
再进一步,实参可以是另一个函数的返回值:
average(sum(1, 2), sum(3, 4)) 里两个 sum 先各自交卷(3 和 7),
average 再算它们的平均 5.013。
嵌套求值的顺序永远是最内层先算——第 02 章 print(input(...)) 的
那一幕,在这里有了正式的理论地位。
13. 默认参数与关键字传参:练习里的完整光谱
默认参数一节只有两道练习,但覆盖了全部四种调用组合,走一遍等于把 「默认 + 关键字」两个机制一起验收31:
def sum_of_squares(a=1, b=2):
print(a ** 2 + b ** 2)
sum_of_squares() # 全用默认:a=1, b=2 → 1 + 4 = 5
sum_of_squares(a=2) # a 覆盖,b 默认 → 4 + 4 = 8
sum_of_squares(b=3) # b 覆盖,a 默认 → 1 + 9 = 10
sum_of_squares(a=3, b=4) # 全覆盖 → 9 + 16 = 25四种输出(5 / 8 / 10 / 25)逐字来自原书31。这一道题同时演示了 三个事实:默认参数可以全带(两个都有默认值,裸调用合法); 关键字传参可以只点名单个(漏掉的走默认);默认值在定义时 就绑定,不是调用时才临时造。
welcome(start_time='17:30', verb='start') 是本章 longest 的练习,
也是全部前八章知识的会师演习,逐步走一遍22:
第 1 步 start = 60 × int('17'[0:2]) + int('17:30'[-2:])
= 60 × 17 + 30 = 1050 分钟 ——切片取时、分钟化
第 2 步 while True 开场 ——死循环起手,退出靠 break
第 3 步 输入 17:05 → current = 60×17+5 = 1025
第 4 步 left = 1050 - 1025 = 25 ≥ 1 → 打印「25 分钟后 start」
第 5 步 输入 17:25 → left = 5 → 打印
第 6 步 输入 17:30 → left = 0 < 1 → break,函数收工
第二次调用 welcome('19:00', 'resume'):start = 1140,
verb 变成 resume,提示语里的动词跟着换。三个机制在一次调用里全部在线:默认参数(start_time、verb)、 while True + break(第 8 章)、字符串切片与类型转换(第 03、05 章)。 这类「函数当调度室」的写法,就是真实程序的样子。
14. return 练习全谱系:从一件到四件
return 区的练习按返回复杂度排成一个阶梯,每级一个范例25:
一级:单值数值。 abs(-1) 返回 1;product(2, 5) 返回 10;
power(2, 3) 返回 8;average(1, 2, 3) 返回 2.0。
sum_n(100) 是这一级唯一的循环题:累加器攒到 return sum,
输出 5050——高斯求和公式 n(n+1)/2 的循环版实证32。
二级:单值字符串。 welcome('Neo') 返回 'Welcome, Neo!';
concatenate('hello, ', 'world!') 返回 'hello, world!'。
字符串拼接(第 05 章)装进 return,产出能再被 format、再被切片33。
三级:单值布尔。 even(1) False、even(2) True;
prime(3) True、prime(4) False;positive(1) True、positive(-1) False。
这一级的进化最快:even 还是 if-else 明写,positive 已经
return n > 0 一步到位;divisible_6 则是「if 真 return True,
落到末尾 return False」的省略 else 写法2634。
四级:多值元组。 get_profile() 问四件事、return 四件,
输出 ('Thomas', 'Anderson', 18, '09090');stats(x, y, z)
算四样统计量,stats(1, 2, 3) 返回 (1, 3, 2.0, 6)——
min、max、avg、total 按 return 语句里的顺序对号入座2829。
挑战版 stats() 是这架阶梯的顶端:没有参数,一切现场问——
问个数、循环问数、擂台法最值、字符串攒报告、return 四元组。
五数输入(1.1, 2.2, 3.3, -9, 4.4)的完整输出里有一个值得鼓掌的细节:
mean 是 0.40 而不是直觉的 2.2——因为 -9 这个离群值把总和拉到了 2.0,
total/n = 2.0/5 = 0.4。原书特意放进一个负数,让「平均数怕离群值」
这条统计学常识变成你亲手算出来的体验30。
15. 无参数函数练习族:把第 08 章的循环整个搬进来
9.3.2 的练习看起来琐碎(hello_world、print1to10、prime_in_100、 even_in_10、odd_in_10……),其实是整个第 08 章的函数化大搬迁712:
def prime_in_100(): # 第 08 章素数案例的函数版
for i in range(2, 100):
prime = True
for j in range(2, i):
if i % j == 0:
prime = False
break
if prime:
print(i)
prime_in_100() # 调用一次,素数表打一整张对照第 08 章的裸代码版本,唯一的差别就是套了 def 壳、起了名。 这个「套壳」动作本身就是本章第一课的实证:函数 = 给代码块起名字。 起完名的收益立刻兑现:第 08 章的成绩册、温度换算想再用一遍, 得整段复制;函数版想再用,打一行调用就行2。
print_welcome() 值得单说:它的函数体里嵌着 input()12——
def print_welcome():
print(f'Welcome, {input("What is your name? ")}!')
print_welcome() # 输入 Neo → Welcome, Neo!注意一个分寸问题:这个函数偷偷问了用户一个问题——调用处只写了
print_welcome(),看不出它要等输入。书里没有讨论这个设计争议,
我们补上:函数内部藏 input 会隐藏依赖,更干净的做法是把 input 留在
调用处、把名字当参数传进来(像 greet(name) 那样)。两种风格在
入门阶段都成立,但心里要有这根弦。
16. 双参数练习族:分工、擂台与反例
9.5.2 的练习把双参数的常见模式扫了一遍15:
分工模式:print_message_n(message, n)——两个参数各管一件事,
一个出内容、一个出次数。print_message_n('Hello, world!', 3)
打三行,参数顺序和字面阅读顺序一致,这是按位置传参最安全的形态15。
擂台模式:min(x, y, z)——先立擂主(min = x),
两个挑战者轮流上(if y < min: min = y),最后擂主就是答案。
这个模式在 9.12.2 的 stats 里升级为四统计量同场:
min 和 max 各立一个擂台,total 用累加器,avg 最后除出来1529。
反例模式:average_speed(distance, time)——原书这段代码有处
值得警惕的写法:print(average_speed(10, 3))。函数体只 print 不
return,外层 print 拿到的返回值是「None」——打印版函数被当成
return 版用,输出会多出一个 None 再跟 3.333…。这是 §10 对照表的
现场反证35。
逻辑运算参数化:and_operation(True, True) / (True, False)
输出 True / False——把第 04 章的真值表做成了函数,无聊但有用:
它证明运算符的结果能当数据传进传出15。
17. 参数与实参的再辨:一段容易混的话
术语再钉一次,因为这两词太像:参数活在 def 行,实参活在调用行9。
def greet(name, age): # name、age:参数(槽位的名字)
...
greet('Neo', 18) # 'Neo'、18:实参(填进槽的值)
greet(name='Neo', age=18) # 注意:按关键字传时,写的仍是「参数名=值」关键字传参的等号左边是参数名——这个细节新人常混:实参没有名字,
它是值;name='Neo' 读作「把 name 这个槽填成 'Neo'」16。
18. 内建函数盘点:用了九章的它们,档案在此
学了 def,正好给一路陪伴的内建函数们建一份完整档案——每个都按 IPO 描述,这既是本章的练习要求5,也是最好的复习表:
| 函数 | 输入(I) | 处理(P) | 输出(O) |
|---|---|---|---|
print(value, ...) | 任意多个值 | 转成文本,用 sep(默认空格)连接,末尾加 end(默认换行) | 显示到屏幕,返回 None |
input(prompt) | 提示字符串(可省) | 显示提示,等用户输入一整行 | 输入内容的字符串,返回 str |
str(x) | 任意值 | 转成它的字符串形态 | str:str(5) → '5' |
int(x) | 数或数字字符串 | 转整数(砍小数;乱转报 ValueError) | int:int('5') → 5 |
float(x) | 同上 | 转浮点数 | float:float('5.5') → 5.5 |
len(s) | 字符串(及一切序列) | 数元素个数 | int:len('Hello') → 5 |
range(a, b, step) | 一至三个整数 | 造整数列(左闭右开) | range 对象(喂给 for) |
type(x) | 任意值 | 查它的类型 | 类型本身:type(5) → int |
help(函数名) 是这些档案的官方来源:help(print) 打印出的签名
print(value, ..., sep=' ', end='\n', ...) 里,sep、end 这些
「等号加默认值」的写法,现在你知道它们就是默认参数420。
读官方文档的第一项技能,就是看懂函数签名里的参数表。
还有三个本章新登场、后文要用的内建函数,提前建档:
min(...)/max(...):自带擂台,min(3, 1, 2)→ 1—— 第 08 章成绩册里手写的擂台代码,内建版一行搞定;sum(...):自带累加器,sum([1, 2, 3])→ 6(列表见第 11 章);round(x, n):真四舍五入,round(3.14159, 2)→ 3.14—— 与int()的砍小数、:.2f的格式化显示,并称「小数处理三件套」。
19. 函数的组合:从零件到机器
本章的终极图景:函数是零件,零件互相嵌套就组成机器。 书里已经出现过的组合链,串起来看一次132729:
prime(n) ——一个零件:回答 True/False
sum_n(n) ——一个零件:攒出 1..n 的和
stats(x, y, z) ——四个零件的组合:min 擂台 + max 擂台 + 累加器 + 除法
stats() 挑战版 ——机器:循环驱动零件,报告 + 元组双输出
组合规则两条:return 版零件可以被无限嵌套(print 版不行);
多值返回的元组可以被解包后继续喂给别的零件。原书 9.13 的 GenAI 提示区有一问「函数怎样让复杂程序更易懂」36—— 答案就是这节的那张图:每个函数只干一件事,机器的可读性来自 零件的可读性。这个「单一职责」原则——一个函数只干一件事——是函数存在的最高理由,也是从「会写函数」到「会设计程序」的分水岭。
20. IPO 思维练习:把生活翻译成函数
9.1.3 的第一道练习常被跳过,其实它是本章最好的思维体操: 「想想生活里哪些东西像函数,用 IPO 描述它」5。 书里给了内建函数的示范,我们补三个生活样本,把这套思维演示透:
自动售货机
I:按下的商品编号 + 投进去的钱
P:核对价格 → 找零 → 推出商品
O:商品 + 找零(或「钱不够」的提示)
洗衣机
I:放进去的衣服 + 选的模式(快洗/标准/强力)
P:按模式设定的时间与力度转筒
O:洗好的衣服(洗衣机不「返回」衣服,它是打印版:效果留在原地)
快递柜
I:取件码
P:核对码 → 弹开对应的柜门
O:柜门开了(你得自己取件——返回的是「许可」,不是包裹)三个样本各对应一个编程概念:售货机是「多参数 + 条件分支」, 洗衣机是「参数带默认值」(模式不传就用标准洗), 快递柜是「返回值 vs 副作用」的微妙版——返回的只是个许可, 真正的效果(取到包裹)发生在你这边。
IPO 的深层价值:它是「把需求翻译成代码」的第一道工序。
顺带一提,书里的这道练习是「自反」的——它让你用函数的思维描述
函数本身:input(prompt) 就是一个 IPO 最完美的样本,
你天天在用它,却从没把它拆开看过5。
真实工作里,需求文档都是人话(「用户输入取件码就能开箱」),
程序员的第一反应就是拆 IPO——拆得动的需求才能写代码,
拆不动的需求说明还缺信息,得回去问。这道工序从第 06 章的
收银案例就开始做了,现在它有了名字。
21. 函数定义的常见错误速查
def 行的事故高发区,集中在这四处(机制依据本章语法,案例是我们的归纳)37:
| 错误 | 报错 | 原因 |
|---|---|---|
def say_hello() 忘了冒号 | SyntaxError: invalid syntax | 冒号是 def 条款的收尾 |
| 函数体忘缩进 | IndentationError | 缩进即语法(第 07 章同款) |
| 默认参数排在无默认参数前 | SyntaxError: non-default argument follows default argument | 「谁填谁」歧义,语法直接拒绝 |
def 之后忘了调用 | 无任何输出 | 定义只登记不执行 |
第四种不算错,但最常被新手当成「bug」:代码写完运行,什么反应都没有—— 九成是忘了在 def 之后写调用。检查口诀:「定义下面必有调用」。
22. 传参方式的对照练习:greet 的一题三解
9.6.2 的练习只有一句话:「用关键字传参调用你之前定义的函数」, 看似敷衍,配上 greet 的三行对照就成了全章最有信息量的三行16:
def greet(name, age):
print(f'Hello, {name}! You are {age} years old.')
greet('Neo', 18) # 按位置:快,但靠记性
greet(age=18, name='Neo') # 按关键字:反着写也对——名字对了,顺序无所谓
greet('Neo', age=18) # 混用:位置在前,关键字在后——也可以!第三种「混用」是原书没演示、但 Python 合法的形态:按位置的在前、 按关键字的在后,两种契约在同一行里各司其职。规则只有一条死限制: 关键字传参一旦登场,它后面的实参就不能再回到按位置。
判断(我们的,不是书里的): 传参方式的选判据可以压成一句话—— 「这行调用,单独读出来懂不懂」。
greet('Neo', 18)单独读,懂;rectangle_area(4.5, 6)单独读,顺序存疑(长乘宽还是宽乘长?); 存疑就换关键字:rectangle_area(length=4.5, width=6),自解释。 原书的裁决(参数多用关键字)其实是同一判据的规模化版本17。 如果错,会错在: 参数名本身起得差时(比如f(a, b, c)), 关键字传参也救不了可读性——根子在命名,不在传参方式。
23. 回望:第 02 章的两个谜团,本章全部解开
学到本章,可以回头给第 02 章悬置的两件事正式结案——这种「后置的豁然开朗」 正是本书章节设计的回报:
谜团一:print 后面的圆括号到底是什么? 现在你知道:print 是
内建函数的名字,圆括号是调用记号,括号里的值是实参。print('Hello')
不是「句子的奇怪标点」,是「调用 print 函数,传一个实参」。
甚至第 02 章那个「逗号补空格」的怪癖,也有了机制层面的解释:
sep=' ' 这个默认参数决定了多个值之间的分隔符20——
想换成别的分隔,print('a', 'b', sep='-') 输出 a-b。
谜团二:input 为什么「永远返回字符串」? 「返回」这个词当时只能
囫囵记下,现在每个字都有落点:函数把值交还给调用处,交还的值的类型
由函数的 return 决定,input 的设计就是 return 一个 str。
你会写 return 5 了,也就懂了「返回字符串」是什么意思23。
这就是函数这一章在全书的真正位置:它不单是新知识,还是旧知识的 「显影液」——前面所有「先记住,以后会讲」的暗号,到这里全部显影。 读后续章节时,可以留意哪些新机制(第 11 章的列表方法、第 14 章的 字典方法)其实是同一套「函数 + 参数 + 返回」的变体。
24. 作者的判断与证据
判断(我们的,不是书里的): 本章的练习量(四十余道)看着吓人, 其实是一道母题的四十次变奏——「参数进打印/进判断/进循环」三个作用域 的排列组合。刷完不必记答案,但要能闭眼写出这三个作用域各一个代表; 写得出,本章就算长在身上,而不是记在笔记本上。 如果错,会错在: 对已经完全掌握控制流的读者,这些练习的边际收益 递减很快——母题三遍之后就该跳过,把时间让给第 10 章的递归和第 11 章 的列表,那里才是新知。
- 「函数是黑箱、接口重于实现」是业界四十年的共识,书里用烤面包机讲给新手, 比喻准确且无害3。
- 「按值传递」的表述(9.4.2)是教学简化:Python 对不可变类型(int、str) 传值的效果确实如此;但对可变类型(列表,第 11 章),传的是引用, 函数里改列表外面看得见。书里 double(a) 的例子用 int,结论没错但 别外推——这是留给第 11 章的暗雷,我们这里先立个警示牌。
- 「return 版 vs 打印版」的对照编排是本章最好的设计:同一功能 (abs、even、prime)两次出现、两种交付,读者在重复中自然体会 「结果交还 vs 效果产生」的区别1225。
- 参数命名与内建函数撞名(
abs、min、sum)在原书练习里反复出现—— 合法但坏习惯,书里没提醒,我们提醒:内建名字让一让,叫my_abs或abs_val。
26. 边界与局限
- 没有讲作用域(scope,作用范围)。 函数里的变量(局部变量)和外面的变量 什么时候同名、什么时候互相看不见——这个「变量的能见度」话题本书留白, 只从 double(a) 的例子侧面露了一角11。一个预告:函数内创建的变量 默认只在函数内可见,出了函数就消失——这就是「局部」的字面意思; 与之相对,外面的变量在函数内默认可读。完整的规则(尤其同名时谁遮谁) 留给下一本书。
- 文档字符串(docstring)没讲。 给函数写说明文字的标准做法,是在函数体
第一行放一段三引号字符串——
help()显示的正是它。入门阶段用#注释 替代即可,写库给外人用时再学。 - 函数的测试没讲。 本章写了四十多个函数,没有一个配套的自测——
真实的工程实践里,函数和它的测试是双胞胎:写
even(n)的同时, 就该有「输入 2 得 True、输入 1 得 False」的断言守着。 入门阶段靠 Jupyter 手敲验证,量级一大就要学assert和测试框架, 这是本书与「职业开发」之间最大的一道缝。 - 函数的类型标注(type hint)没讲。 给参数和返回值标注类型
(
def greet(name: str) -> str:)能让编辑器帮你抓错,是数据科学圈的新默认—— 2025 年的新代码大多带标注,本书一例未用,算一个时代印迹。 - 没有讲 lambda、装饰器、生成器。 函数的高级形态一概没有——
对入门书是对的,但你在别人的代码里会撞见
lambda x: x*2这种 一行函数,知道它是「匿名函数」(不必起名的临时小函数)即可; 装饰器和生成器更是要等到理解函数是一等公民(函数能当值传来传去) 之后才学得动,本书没碰是对的。 - 没有讲模块与 import。 函数造好之后怎么打包分享(
import别人、 自己写.py文件)是下一课,本书环境始终在一个 Notebook 里, 这个自然延伸被略过了。真实开发中,函数会被收进模块、模块收进包——import numpy as np里的 np 就是一个装满函数的命名空间; 你在 Jupyter 第一格写的import,和本章的 def 是同一条流水线的两端。 - 返回值解包(unpacking)没讲。
first, last, age, zip = get_profile()这种「一次收四件」的写法比收到元组再索引顺手得多,原书只演示了 打印元组28。这里是明显的教学留白,我们补:a, b = swap(a, b)这种交换写法全靠它——等号两边结构相同,Python 按位置拆包。 解包是和多值返回配套的另一半,两者合起来才完整。
27. 可带走的
本章知识量全书最大,带走时分「定义层、参数层、返回层」三层装盒。
定义层:
- 函数 = 命名 + 参数槽 + 身体 + return;def 只登记,圆括号才执行; 定义下面必有调用,否则什么都不会发生6。
pass占位;函数名用动词、全小写下划线;别抢内建函数的名字8。help(函数名)看官方说明书——读懂函数签名里的参数表是查文档的 第一技能4。
参数层:
- 参数是槽位,实参是填料;调用第一步就是
参数 = 实参910。 - 按值传递:函数内改参数,外面的实参变量不受影响(仅对 int、str 等 不可变类型成立;列表等可变类型是另一套规则,第 11 章见)11。
- 按位置传参快但怕顺序错(反了不报错,输出 nonsense);按关键字传参啰嗦但 不怕错;参数多用关键字;混用时位置在前、关键字在后1617。
- 默认参数让槽位可省略;带默认的排后,这是语法硬规矩; 四种调用组合(全默认/覆盖一个/覆盖另一个/全覆盖)都合法1819。
print的 sep/end、range的 start/step 都是默认参数20。
返回层:
- return 把结果交还调用处,能存、能传、能判断、能嵌套进别的调用—— 打印版做不到这些;默认写 return 版,打印留给调用方23。
- 布尔函数:
return n > 0比 if-else 明写更直;return False在循环里 是「提前交卷」2627。 - 多值返回交还的是元组,顺序就是 return 里的顺序;
a, b = f()解包收多件2829。 - IPO 是需求翻译的第一道工序:给不出 IPO 的函数设计,还没想清楚。
- 单一职责:一个函数只干一件事;复杂机器 = 简单零件的组合。
28. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 函数的定义与三用途 | CHAPTER 9: Functions | text/18-ch09-chapter-9-functions.txt:14(搜「building blocks of reusable code」) · text/18-ch09-chapter-9-functions.txt:18(搜「Write the code once」) |
| 烤面包机黑箱模型 | 同上 | text/18-ch09-chapter-9-functions.txt:30(搜「toast some bread」) · text/18-ch09-chapter-9-functions.txt:34(搜「work like a black box」) |
| help(print) 与默认参数线索 | 同上 | text/18-ch09-chapter-9-functions.txt:52(搜「help(function)」) · text/18-ch09-chapter-9-functions.txt:64(搜「sep: string inserted」) |
| 函数分类(返回值/参数数) | 同上 | text/18-ch09-chapter-9-functions.txt:72(搜「two categories」) |
| def 语法与调用 | 同上 | text/18-ch09-chapter-9-functions.txt:87(搜「def say_hello」) · text/18-ch09-chapter-9-functions.txt:94(搜「keyword def」) |
| pass 占位 | 同上 | text/18-ch09-chapter-9-functions.txt:112(搜「def empty」) |
| 参数 vs 实参 | 同上 | text/18-ch09-chapter-9-functions.txt:190(搜「A parameter is a variable」) · text/18-ch09-chapter-9-functions.txt:192(搜「An argument, on the other hand」) |
| greet 逐步回放与按值传递 | 同上 | text/18-ch09-chapter-9-functions.txt:205(搜「step-by-step」) · text/18-ch09-chapter-9-functions.txt:215(搜「passing by value」) |
| double(a) 实证 | 同上 | text/18-ch09-chapter-9-functions.txt:216(搜「def double」) · text/18-ch09-chapter-9-functions.txt:224(搜「x is 10」) |
| 双参数演示 | 同上 | text/18-ch09-chapter-9-functions.txt:336(搜「rectangle_area」) |
| 倒序 range 挑战 | 同上 | text/18-ch09-chapter-9-functions.txt:376(搜「print_n_to_m_challenge」) |
| 位置传参与关键字传参对照 | 同上 | text/18-ch09-chapter-9-functions.txt:457(搜「meaningless message」) · text/18-ch09-chapter-9-functions.txt:467(搜「age=18, name='Neo'」) · text/18-ch09-chapter-9-functions.txt:482(搜「In conclusion」) |
| 默认参数与排后规则 | 同上 | text/18-ch09-chapter-9-functions.txt:498(搜「def greet(name, msg='Hi')」) · text/18-ch09-chapter-9-functions.txt:508(搜「must be after the parameters without default values」) |
| print/range 的默认参数回看 | 同上 | text/18-ch09-chapter-9-functions.txt:533(搜「understand the print() and range() better」) |
| welcome 综合练习 | 同上 | text/18-ch09-chapter-9-functions.txt:567(搜「def welcome(start_time」) · text/18-ch09-chapter-9-functions.txt:591(搜「17:05」) |
| return 的定义 | 同上 | text/18-ch09-chapter-9-functions.txt:612(搜「return a value back to the calling program」) |
| 四种返回类型 | 同上 | text/18-ch09-chapter-9-functions.txt:653(搜「give_me_5」) · text/18-ch09-chapter-9-functions.txt:732(搜「give_me_FIVE」) · text/18-ch09-chapter-9-functions.txt:763(搜「give_me_True」) · text/18-ch09-chapter-9-functions.txt:827(搜「return first, last, age, zip」) |
| positive 的直接返回 | 同上 | text/18-ch09-chapter-9-functions.txt:802(搜「return n > 0」) |
| 多值返回与元组证据 | 同上 | text/18-ch09-chapter-9-functions.txt:835(搜「('Thomas', 'Anderson', 18, '09090')」) |
| stats 挑战版 | 同上 | text/18-ch09-chapter-9-functions.txt:899(搜「How many numbers you are going to enter? 5」) · text/18-ch09-chapter-9-functions.txt:911(搜「(-9.0, 4.4, 0.4, 2.0)」) |
补充(不在书里,来自通用知识):「位置在前、关键字在后」的混用规则与 「关键字之后不能再回位置」的限制,依据 Python 函数调用的语法规定; 原书只演示了纯位置与纯关键字两种形态(第 449–486 段)。
Footnotes
-
出处:「CHAPTER 9: Functions」第 14–17 段(
text/18-ch09-chapter-9-functions.txt:14,搜「building blocks of reusable code」;:16,搜「executed multiple times with different inputs」)。「self-contained recipes」的比方见原文。 ↩ -
出处:「CHAPTER 9: Functions」第 18–21 段(
text/18-ch09-chapter-9-functions.txt:18,搜「Write the code once, and use it many times」;:19,搜「Divide your code into smaller」)。三用途逐字依据原文。 ↩ ↩2 -
出处:「CHAPTER 9: Functions」第 29–39 段(
text/18-ch09-chapter-9-functions.txt:32,搜「internal workings of the toaster」;:35,搜「returns a specific output or result」;:38,搜「how the community of Python is doing」)。 ↩ ↩2 ↩3 -
出处:「CHAPTER 9: Functions」第 52–66 段(
text/18-ch09-chapter-9-functions.txt:59,搜「print(value, ..., sep=' '」;:64,搜「sep: string inserted between values」)。help(print) 的输出片段逐字依据原文。 ↩ ↩2 ↩3 -
出处:「CHAPTER 9: Functions」第 41–66 段(
text/18-ch09-chapter-9-functions.txt:44,搜「Describe the following functions」;:49,搜「range(5)」)。IPO 练习的函数清单见原文。 ↩ ↩2 ↩3 ↩4 -
出处:「CHAPTER 9: Functions」第 86–100 段(
text/18-ch09-chapter-9-functions.txt:87,搜「def say_hello」;:94,搜「keyword def」;:97,搜「indentation as we learned in branching」)。语法拆解逐字依据原文。 ↩ ↩2 ↩3 -
出处:「CHAPTER 9: Functions」第 101–111 段(
text/18-ch09-chapter-9-functions.txt:102,搜「for i in range(3)」)。函数体装循环的演示见原文。 ↩ ↩2 -
出处:「CHAPTER 9: Functions」第 112–118 段(
text/18-ch09-chapter-9-functions.txt:113,搜「pass」;:118,搜「placeholder」)。pass 的占位语义逐字依据原文。 ↩ ↩2 -
出处:「CHAPTER 9: Functions」第 189–195 段(
text/18-ch09-chapter-9-functions.txt:190,搜「A parameter is a variable defined within a function」;:192,搜「An argument, on the other hand」;:193,搜「fills up that input slot」)。 ↩ ↩2 ↩3 -
出处:「CHAPTER 9: Functions」第 197–215 段(
text/18-ch09-chapter-9-functions.txt:206,搜「greet('Alice')」;:209,搜「name = 'Alice'」;:211,搜「return to where greet('Alice')」)。四帧拆解逐字依据原文的逐步注释。 ↩ ↩2 -
出处:「CHAPTER 9: Functions」第 216–228 段(
text/18-ch09-chapter-9-functions.txt:217,搜「x = x * 2」;:224,搜「x is 10」;:225,搜「a is 5」)。「传参 = 赋值」的机制解读与第 03 章引用模型的衔接是我们的加工;输出逐字依据原文。 ↩ ↩2 ↩3 ↩4 -
出处:「CHAPTER 9: Functions」第 230–327 段(
text/18-ch09-chapter-9-functions.txt:233,搜「def hello_name」;:261,搜「def print_prime_2_n」;:275,搜「def even」;:288,搜「def abs」;:312,搜「def prime」)。单参数练习群的代码见原文。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
出处:「CHAPTER 9: Functions」第 335–352 段(
text/18-ch09-chapter-9-functions.txt:336,搜「def rectangle_area」;:342,搜「def sum(a, b)」;:347,搜「def average(a, b)」)。输出 27.0 / 15 / 25.0 见原文。 ↩ ↩2 ↩3 -
出处:「CHAPTER 9: Functions」第 367–385 段(
text/18-ch09-chapter-9-functions.txt:376,搜「def print_n_to_m_challenge」;:381,搜「range(n, m-1, -1)」)。倒序 range 的步长 -1 写法见原文。 ↩ -
出处:「CHAPTER 9: Functions」第 423–434 段(
text/18-ch09-chapter-9-functions.txt:425,搜「def min(x, y, z)」)。擂台法代码见原文。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
出处:「CHAPTER 9: Functions」第 449–481 段(
text/18-ch09-chapter-9-functions.txt:453,搜「greet('Neo', 18)」;:457,搜「meaningless message」;:467,搜「greet(age=18, name='Neo')」;:478,搜「in any order」)。两种传参的对照与「哑错」的输出(Hello, 18! You are Neo years old.)逐字依据原文。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
出处:「CHAPTER 9: Functions」第 482–486 段(
text/18-ch09-chapter-9-functions.txt:482,搜「In conclusion」)。「参数少用位置、复杂用关键字」的裁决与「数据科学更常见关键字」的说法逐字依据原文。 ↩ ↩2 ↩3 -
出处:「CHAPTER 9: Functions」第 492–521 段(
text/18-ch09-chapter-9-functions.txt:498,搜「def greet(name, msg='Hi')」;:505,搜「provided the default values」)。默认参数的工作方式与两种调用输出(Hi, Neo! / Hello, Neo!)见原文。 ↩ ↩2 -
出处:「CHAPTER 9: Functions」第 507–509 段(
text/18-ch09-chapter-9-functions.txt:508,搜「must be after the parameters without default values」)。排后规则逐字依据原文。 ↩ ↩2 -
出处:「CHAPTER 9: Functions」第 533–534 段(
text/18-ch09-chapter-9-functions.txt:533,搜「understand the print() and range() better」)。「print 和 range 都带默认参数」的回看见原文;与第 02 章逗号补空格的关联是我们的串讲。 ↩ ↩2 ↩3 ↩4 -
出处:「CHAPTER 9: Functions」第 522–531 段(
text/18-ch09-chapter-9-functions.txt:522,搜「def print_numbers(end, start=0, step=1)」)。三种调用形态见原文。 ↩ -
出处:「CHAPTER 9: Functions」第 556–604 段(
text/18-ch09-chapter-9-functions.txt:567,搜「def welcome(start_time='17:30', verb='start')」;:569,搜「60 * int(start_time[:2])」;:591–:604,两组输入输出)。切片取时、分钟化、循环报时的完整演示见原文。 ↩ ↩2 -
出处:「CHAPTER 9: Functions」第 605–640 段(
text/18-ch09-chapter-9-functions.txt:612,搜「return a value back to the calling program」;:621,搜「stored in a variable」)。return 的定义与用途逐字依据原文。 ↩ ↩2 ↩3 -
出处:「CHAPTER 9: Functions」第 635–640 段(
text/18-ch09-chapter-9-functions.txt:637,搜「type":"string」;:640,搜「Hello, Neo!」)。Jupyter 对字符串返回值的回显与 print 输出的对照见原文。 ↩ -
出处:「CHAPTER 9: Functions」第 642–721 段(数值:
text/18-ch09-chapter-9-functions.txt:653,搜「def give_me_5」;字符串::732,搜「def give_me_FIVE」;布尔::763,搜「def give_me_True」)。四种返回类型的演示与练习(abs/product/power/average/min/sum_n)见原文。 ↩ ↩2 ↩3 ↩4 ↩5 -
出处:「CHAPTER 9: Functions」第 770–808 段(
text/18-ch09-chapter-9-functions.txt:773,搜「def even(n)」;:801,搜「def positive(n)」;:802,搜「return n > 0」)。if-else 版与直返版的对照见原文;「绕路」的评价是我们的说法。 ↩ ↩2 ↩3 -
出处:「CHAPTER 9: Functions」第 785–797 段(
text/18-ch09-chapter-9-functions.txt:787,搜「def prime(n)」;:790,搜「return False」;:791,搜「return True」)。「提前交卷」的 return 版素数见原文。 ↩ ↩2 ↩3 -
出处:「CHAPTER 9: Functions」第 817–835 段(
text/18-ch09-chapter-9-functions.txt:827,搜「return first, last, age, zip」;:831–:835,输入输出)。四值返回与输出中的圆括号(元组)见原文;「收到的其实是元组」的解读依据输出形态与通用知识。 ↩ ↩2 ↩3 ↩4 -
出处:「CHAPTER 9: Functions」第 837–857 段(
text/18-ch09-chapter-9-functions.txt:840,搜「def stats(x, y, z)」;:855,搜「print(stats(1, 2, 3))」;:857,搜「(1, 3, 2.0, 6)」)。stats 的输出与元组形态见原文。 ↩ ↩2 ↩3 ↩4 ↩5 -
出处:「CHAPTER 9: Functions」第 859–911 段(
text/18-ch09-chapter-9-functions.txt:899,搜「How many numbers you are going to enter? 5」;:905,搜「1.1 2.2 3.3 -9.0 4.4」;:911,搜「(-9.0, 4.4, 0.4, 2.0)」)。五数输入的完整输出与四元组返回逐字依据原文。 ↩ ↩2 -
出处:「CHAPTER 9: Functions」第 537–554 段(
text/18-ch09-chapter-9-functions.txt:541,搜「def sum_of_squares(a=1, b=2)」;:546–:554,四种调用及输出 5/8/10/25)。代码与输出逐字依据原文。 ↩ ↩2 -
出处:「CHAPTER 9: Functions」第 713–722 段(
text/18-ch09-chapter-9-functions.txt:715,搜「def sum_n(n)」;:721,搜「print(sum_n(10))」)。sum_n 的累加结构与调用见原文;5050 与 n(n+1)/2 的对照是我们的验算。 ↩ -
出处:「CHAPTER 9: Functions」第 739–757 段(
text/18-ch09-chapter-9-functions.txt:741,搜「def welcome(name)」;:752,搜「def concatenate」)。两个字符串返回练习见原文。 ↩ -
出处:「CHAPTER 9: Functions」第 770–815 段(
text/18-ch09-chapter-9-functions.txt:773,搜「def even(n)」;:787,搜「def prime(n)」;:801,搜「def positive(n)」;:812,搜「def divisible_6(n)」)。四个布尔函数的两个写法梯度见原文。 ↩ -
出处:「CHAPTER 9: Functions」第 436–441 段(
text/18-ch09-chapter-9-functions.txt:438,搜「def average_speed」;:441,搜「print(average_speed(10, 3))」)。函数体无 return 而外层 print 其调用结果——「None 事故」由代码结构推得(原文未印输出,标为我们的分析)。 ↩ -
出处:「CHAPTER 9: Functions」第 916–928 段(
text/18-ch09-chapter-9-functions.txt:926,搜「breaking them down into smaller」)。「单一职责」的归纳是我们的表述,问题本身出自 GenAI 互动区。 ↩ -
补充(不在书里,来自通用知识):四种报错的措辞依据 Python 解释器的实际报错;「定义只登记不执行」的机制依据本章 §4 与原书第 94–100 段。 ↩