函数、调用栈帧及返回值
引入| English | 释义 | 拼音 |
|---|---|---|
| function/ˈfʌŋkʃn/ | 函数 | hán shù |
| side effect/saɪd ɪˈfekt/ | 副作用 | fù zuò yòng |
| contract/ˈkɒntrækt/ | 契约 | qì yuē |
| parameter/pəˈræmɪtə/ | 形参 | xíng cān |
| argument/ˈɑːɡjuːmənt/ | 实参 | shí cān |
| call frame/kɔːl freɪm/ | 调用帧 | diào yòng zhēn |
| scope/skəʊp/ | 作用域 | zuò yòng yù |
| local variable/ˈləʊkl ˈveərɪəbl/ | 局部变量 | jú bù biàn liàng |
| decomposition/ˌdiːkɒmpəˈzɪʃn/ | 分解 | fēn jiě |
让每个函数只承担一项明确职责
- 函数 函数将一组命名任务组织在一起。在编写函数体之前,先说明其输入、返回值以及任何副作用 副作用(例如打印输出)。
- 契约 契约规定了调用者可以传入什么内容以及函数承诺返回什么结果。在此例中,金额单位为便士,必须是非负整数。
def add_fee(amount):
"""Return amount plus a fixed fee of 3 pence."""
return amount + 3
add_fee(8) 的求值结果为 11。它不会打印任何内容。将计算与显示分离,可使其他函数复用该结果。
哪个契约允许其他计算直接使用含费用的金额?
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
返回值可用于表达式中;而打印输出属于独立的副作用。
区分输入名称与输入对象
- 在定义中,
amount是形参 形参。在add_fee(8)中,表达式8提供了实参 实参。实参表达式会在函数体执行前被求值。 - 函数调用将形参名称绑定到所提供的对象上。它不会自动复制该对象,也不会建立调用者的名称与形参名称之间的链接。
amount = 4
result = add_fee(amount * 2)
print(amount, result)
实参表达式为 8 提供了一个值;在调用内部,amount 指向该整数。返回的 11 成为 result。模块中的 amount 仍然指向 4,因此输出结果为 4 11。
将下列各项术语与其在add_fee(8)中的角色一一对应。
难度 2/5 · 今日首次完成可获 140 金币及少量额外奖励
参数在调用时被求值;其对象绑定到形参;返回值将结果传回。
为每次调用保留独立的栈帧
- 调用帧 调用帧记录一次活跃的函数调用,包括其形参和局部变量名。当一个函数调用另一个函数时,调用者会等待返回值。
- 按执行顺序阅读此程序:模块 →
twice→add_fee→twice→ 模块。下方的箭头表示概念上的控制流,而非物理内存布局。
def twice(amount):
subtotal = amount * 2
return add_fee(subtotal)
amount = 4
result = twice(amount)
print(amount, result)

使用追踪来检查调用和返回过程。栈帧详情展示了嵌套调用时的状态。这是针对本示例的手动追踪,并非 Python 解释器的实际输出。
追踪两次调用twice(4)并传入参数add_fee(8)
将局部绑定与返回值区分开。
请将这些事件按 result = twice(4) 的执行顺序排列。
难度 3/5 · 今日首次完成可获 200 金币及少量额外奖励
嵌套调用在 twice 完成其返回表达式之前结束。
在此嵌套调用期间,add_fee 内部的 amount 绑定的是哪个整数?
难度 2/5 · 今日首次完成可获 140 金币及少量额外奖励
twice 计算小计为 4 乘以 2,然后将该 8 传递给 add_fee。
返回结果;打印消息
return终止当前调用并将一个对象返回给调用者。若函数执行至末尾未遇到return,则默认返回None。print负责输出内容。同时它也返回None,因此在函数内打印某个计算结果并不意味着该计算结果就是函数的返回值。
def show_fee(amount):
print(amount + 3)
value = show_fee(8)
print(value)
此处先打印 11,再换行打印 None。如果调用者需要获取数值,应使用 return amount + 3 并交由调用者决定是否打印。
show_fee 打印了 11,但没有 return 语句。value 中存储的是什么?
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
Python 函数执行到末尾时默认返回 None,无论是否包含打印输出。
在正确的作用域中解析名称
- 局部变量 局部变量属于某次函数调用。其作用域 作用域决定了名称可被解析的范围;另一函数无法自动访问调用者的局部变量。
- 将形参重新赋值为新整数仅改变其局部绑定关系,并不会重新赋值调用者中同名变量。
def increase(amount):
amount = amount + 1
return amount
amount = 4
new_amount = increase(amount)
print(amount, new_amount)
输出为 4 5。两个名称最初都指向整数 4;函数内部的赋值操作会重新绑定其局部名称。若要更新模块名称,请编写 amount = increase(amount)。
执行 new_amount = increase(amount) 后,模块中的 amount 仍保留什么值?
难度 2/5 · 今日首次完成可获 140 金币及少量额外奖励
仅函数内的局部名称被重新绑定;调用者并未对 amount 赋值。
将计算分解为可测试的片段
- Decomposition 分解 将一个较大的任务拆分为具有明确契约的小型任务。例如对于订单,先计算单品小计,加上费用,最后显示最终金额。
- 直接测试计算逻辑,包括零数量和多件商品的情况。声明输入为非负整数;输入验证可以是一个独立函数。
def item_subtotal(unit_pence, quantity):
return unit_pence * quantity
def order_total(unit_pence, quantity):
return add_fee(item_subtotal(unit_pence, quantity))
order_total(5, 2) 产生 13;在固定费用契约下,order_total(5, 0) 产生 3。如果零件订单应花费为零,则需同时修改契约和实现方式。
根据所述的固定费用合同,order_total(5, 0) 以便士为单位返回多少?
难度 2/5 · 今日首次完成可获 140 金币及少量额外奖励
商品小计为零,但 add_fee 仍然加上了 3。免费空订单需要不同的合同条款。
信任答案前先解释整个调用过程
- 追踪参数表达式、参数绑定、局部计算及返回值。分别检查调用后的调用方状态和输出结果。
- 不要将参数传递描述为复制每个对象。可变集合可以通过参数被修改;关于突变和别名的对象图将在单元 1.5 中介绍。
打印出的正确数字并不能证明返回的结果是正确的。请使用 assert order_total(5, 2) == 13 等直接检查方法,并单独审查预期输出。
调用 Python 函数会自动复制所有参数对象。
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
参数绑定到提供的对象上。仅在需要时才应显式请求复制。