想象你是厨房里的主厨。今天客人点了一道番茄炒蛋,你翻开随身携带的食谱卡,三步五步就炒出一盘香气扑鼻的菜。明天客人又点同样的菜,你不需要重新发明做法——因为食谱就在那里,照着做就行。这就是函数的本质:把一段逻辑写成“食谱”,反复调用,永远不需要重写第二遍。 Python 的世界里,食谱就是 def 定义出来的函数。食材是参数(parameter),出锅的菜是返回值(return value)。你可以在食谱上写“如果客人没交代,就默认放两勺糖”——这叫默认参数。有些食材论顺序放,有些按名字指——这是位置参数与关键字参数的区别。遇到“想放多少食材就放多少”的创意菜?可变参数 *args 和 **kwargs 就是那只万能的塑料袋。 更妙的是,Python 里还有一张“便签纸食谱”——lambda 表达式,适合那些只需用一次、不必兴师动众定义完整函数的场景。比如排序时临时指定一个比较规则,lambda 就像口袋里随取随用的调味勺。学完这一篇,你将能从“只会照着教程写脚本”进阶到“能自己搭建整洁、可复用的代码厨房”,并为后续学习装饰器、函数式编程打下最扎实的地基。

岗位关联

对 AI 从业者而言,函数与 lambda 是构建整洁、可复用数据处理管线的基石。无论是写数据清洗脚本、构造特征工程,还是理解深度学习框架中无处不在的函数回调,这一课都是你从“能跑”走向“会设计”的分水岭。

目录
  1. 可视化演示
  2. 函数基本概念
  3. 参数传递机制
  4. 返回值与解包
  5. 作用域LEGB
  6. 闭包机制
  7. lambda表达式
  8. lambda高阶应用
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示fib(5)递归调用的展开过程,从根节点逐层展开至基准情况,再逐层返回计算结果。

本节配有交互动效(进入视口后自动加载,约 3KB)。

递归调用树 — fib(5) 根节点fib(5)开始递归展开 每个节点分裂为两个子调用fib(n-1)与fib(n-2) fib(n)=fib(n-1)+fib(n-2) 叶子节点fib(1)=1和fib(0)=0为基准情况

函数基本概念

在厨房里,最让主厨安心的不是某个拿手菜,而是那本随时翻开的食谱卡。今天客人点番茄炒蛋,你翻出卡片照做;明天客人又点,你不需要重新发明创造。食谱把“怎么做”固化下来,这就是最初级的代码复用——一次书写,无数次执行。

Python 中的 def 关键字就是写食谱卡的动作。def 后面跟函数名、圆括号里的参数列表,以及冒号后缩进的代码块。这段代码不会在定义时立即执行,而是像食谱卡一样被小心存放,直到你用 函数名() 的形式去“点菜”——这叫函数的调用(call)。

函数还能附带 docstring,即写在函数体第一行的字符串,相当于食谱卡背面的提示笔记,说明这道菜适合什么场合。Python 内置的 help() 和 IDE 的智能提示都会读取它。此外函数在 Python 中是一等对象(first-class object),意味着函数可以像普通变量一样被赋值、被当作参数传递、被放进列表——这为后面的闭包、装饰器打下了地基。

有了食谱卡,重复劳动戛然而止。同样,在代码世界里,凡是出现三次以上的逻辑,你都会下意识地想:要不要把它单独写成函数?这是从“脚本小子”走向“代码主厨”的第一道门槛。

def f(params): body;f(params)↦result\mathrm{def}\ f(\mathrm{params}){:}\ \mathrm{body};\quad f(\mathrm{params}) \mapsto \text{result}
这个公式表达的是:def 定义函数 f,f 接收参数,经过函数体的加工,把结果送回调用方。就像食谱卡从“备菜”到“上菜”的完整过程。
  1. def 与函数名 fdef 是创建函数的声明,f 是这张“食谱卡”的名字。函数名与变量名遵循同样的命名规范,如 make_dish 这样的蛇形命名(snake_case)。
    def f\mathrm{def}\ f
  2. 参数列表 params写在圆括号里,是这道菜需要的食材清单。可以没有参数,也可以有多个参数。调用时必须按函数定义的约定提供食材。
    (params)(\mathrm{params})
  3. 冒号与函数体冒号表示函数体开始,缩进的代码块是具体做菜步骤。调用时,Python 会执行这段代码块。
    : body{:}\ \mathrm{body}
  4. 调用与返回f(params) 代表一次执行,执行后通过 return 把结果送给调用方,就像把做好的菜端出厨房。
    f(params)↦resultf(\mathrm{params}) \mapsto \mathrm{result}

参数传递机制

客人点菜时,讲究的会说“两个番茄,三个鸡蛋”,这是按顺序报的;急性子的会喊“鸡蛋三个!番茄两个!”虽然顺序乱了,但菜名和数量都清晰,主厨也能听懂。前者好比位置参数(positional argument),后者好比关键字参数(keyword argument)。

位置参数按顺序依次匹配形参,少传或多传都会报错;关键字参数用 形参名=值 的方式传参,可以无视顺序,代码的可读性也更强。两种方式还可以混用,但必须把位置参数放在关键字参数前面,这也是 Python 的硬性语法。

默认参数(default argument)让形参在调用方缺省时自动获得预设值,就像食谱上写的“糖两勺,如果客人没交代就默认”。而 *args 和 kwargs 是两颗彩蛋:*args 把多出来的位置参数打包成一个元组,kwargs 把多出来的关键字参数打包成一个字典——相当于万能食材袋,无论食客怎么加料都能接住。

参数的灵活设计,决定了你写的函数像不像一间好餐厅:既给了常客固定的招牌菜(位置参数),也照顾了挑食客人的个性化需求(关键字与默认值),还接纳了突发的大规模宴席(*args 与 kwargs)。

f(a, b=1, ∗args, kwargs)f(a,\ b=1,\ *\mathrm{args},\ \mathrm{kwargs})
这个公式展示了一个函数能同时接收“必需参数 a、默认参数 b、任意数量位置参数、任意数量关键字参数”的完整形态。
  1. 普通位置参数 a必须由调用方提供,像主餐食材,缺了菜就做不成。
    aa
  2. 默认参数 b=1可省可不省。如果不传就使用默认值,就像“没交代就默认放一勺糖”。
    b=1b=1
  3. 可变位置参数 \*args收集多余的“零散食材”,全部装进一个元组。比如调用 f(1, 2, 3, 4) 时,args=(2, 3, 4)。
    ∗args⇒(2, 3, 4)\ast\mathrm{args} \Rightarrow (2,\ 3,\ 4)
  4. 可变关键字参数 \*\*kwargs收集多余的关键字信息,装进一个字典。比如调用 f(1, salt='少许') 时,kwargs={'salt': '少许'}。
    ∗∗kwargs⇒{′salt′:′少许′}\ast\ast\mathrm{kwargs} \Rightarrow \{'\mathrm{salt}': '少许'\}

返回值与解包

一道菜做完,主厨要把成品端到客人面前。函数也一样,return 语句负责把计算结果“端出”函数,交到调用者手里。如果函数没有 return,Python 会在函数结束时默认返回 None,就像上了一盘空气。

return 还意味着流程终止,就像端菜之后不会再回锅翻炒——return 之后的代码不会执行。这一点常被用来做早退(early return)判断:先处理异常和边界条件,让主逻辑留在函数最后。

Python 允许一次 return 返回多个值,比如 return x, y,实际上返回的是一个元组 (x, y)。配合序列解包(unpacking),调用方可以一句 a, b = f() 把结果拆开。这种“多菜同盘”的技法在返回坐标、状态码+消息等场景中非常常用。

恰当的返回值设计,是好函数与坏函数的分水岭。就像好餐厅的菜品稳定且分量清晰,你的函数应该每次都给出可预期的结果,并且调用方一眼就知道拿到的盘子里是什么。

return x, y⇒(x, y)\mathrm{return}\ x,\ y \quad\Rightarrow\quad (x,\ y)
这个公式揭示了一个易被忽略的机制:return x, y 的逗号会自动把多个值打包成元组,因此调用方收到的其实是一个整体。
  1. return 关键字触发函数结束并返回结果;没有该语句时,函数返回内置的 None 对象。
    return\mathrm{return}
  2. 多返回值打包成元组return x, y 里的逗号让 Python 自动构造 (x, y) 元组,等价于 return (x, y)。
    x, y⇒(x, y)x,\ y \Rightarrow (x,\ y)
  3. 解包赋值调用方用 a, b = f() 自动拆开元组,像拆开一份双拼饭盒,一格菜一格饭各就各位。
    a, b=f()a,\ b = f()

作用域LEGB

一间大厨房分几个区域:炉灶旁触手可及的调料架、水槽边的备用柜、储藏室的大米袋,还有整栋楼公用的基础调料区。厨师在不同的位置,能直接够到的东西不一样——这就是“作用域”的直观感受。

Python 对名称的查找遵循 LEGB 规则:Local(局部)→ Enclosing(嵌套外层)→ Global(全局)→ Builtin(内置)。也就是说,函数内部先找局部变量,找不到再去外层嵌套的函数里找,再找不到去模块全局找,最后去 Python 内置的名称(如 len、print)里找。

如果要在函数内部修改一个全局变量,需要声明 global;要修改嵌套外层函数的变量,则需要 nonlocal。否则,Python 会把赋值语句左侧的变量一律视为局部变量,初学很容易踩 UnboundLocalError 的坑。这就像厨房里:你想挪动储藏室里的大米,得先跟管理员打声招呼(global),否则你只是在工作台旁新放了一袋米。

理解 LEGB,就像主厨记住厨房地图:既不会把手伸进别人的锅里(局部变量隔离),也知道上哪儿找公共资源(内置函数)、何时该申请权限(global/nonlocal)。干净的作用域管理,让代码天然低耦合。

Local→Enclosing→Global→Builtin\mathrm{Local} \to \mathrm{Enclosing} \to \mathrm{Global} \to \mathrm{Builtin}
这个公式是 Python 名称查找的固定顺序:由内向外逐层搜索,先到先得。理解它,就理解了为什么局部变量能“屏蔽”全局变量。
  1. Local(局部)当前函数内部声明的变量,相当于炉灶边的调料架,最优先被查找。
    Local\mathrm{Local}
  2. Enclosing(嵌套)如果当前函数内嵌于另一个函数,外层函数的局部变量也在搜索范围中,如同隔壁料理台的备用碗柜。
    Enclosing\mathrm{Enclosing}
  3. Global(全局)当前模块(.py 文件)顶层的变量,像储藏室的大米和面粉桶。
    Global\mathrm{Global}
  4. Builtin(内置)Python 解释器预置的名称,如 print、len,像整栋楼公用的基础水电设施,谁都能用。
    Builtin\mathrm{Builtin}

闭包机制

一位主厨离开这家餐厅之后,随身带了一个便当盒,盒里装着他最爱的几味秘制调料。此后无论他去哪家餐厅做菜,打开便当盒就能还原那独特的味道——这个便当盒,就是闭包(closure)。

在 Python 中,当一个内层函数引用了外层函数的局部变量,并且外层函数把这个内层函数作为返回值返回时,内层函数连同它所引用的变量环境,就构成了闭包。外层函数的局部变量此时以“自由变量”的身份被内层函数记住,即使外层函数已经执行完毕,这些变量的值也不会消失。

如果要修改自由变量,需要在内层函数中使用 nonlocal 声明。闭包在需要携带状态又不想引入类的场景中特别有用,例如计数工厂、装饰器、回调函数。可以说闭包是把“数据 + 行为”打包的最小单位。

闭包是厨师最重要的私藏技艺,也是理解装饰器的必经台阶。当你写出一个“返回函数的函数”时,你已经站在 Python 高级特性的门口了。

outer(x) returns inner;inner() 仍可访问 x\mathrm{outer}(x)\ \mathrm{returns}\ \mathrm{inner};\quad \mathrm{inner}()\ \text{仍可访问}\ x
这个公式描述了闭包的构成要件:外层函数接收参数 x,内层函数引用 x 并被返回。闭包的存在让 x 的生命周期被延长到和 inner 一样长。
  1. 外层函数 outer 与参数 xx 既可以是 outer 的形参,也可以是局部变量,它将被内层函数捕获。
    outer(x)\mathrm{outer}(x)
  2. 内层函数 inner在 outer 内部定义,并作为返回值返回,形同被带回的便当盒。
    inner\mathrm{inner}
  3. 捕获自由变量 xinner 内部使用了 x,这个 x 并未在 inner 内定义,而是来自 outer,因此被称为自由变量。
    x∈freevars(inner)x \in \mathrm{freevars(inner)}
  4. 生存期延长outer 返回后,x 原本的栈帧本应销毁,但闭包让 inner 继续持有 x,值不丢失。
    生命周期∝inner\text{生命周期} \propto \mathrm{inner}

lambda表达式

有时主厨并不会为一道简单小菜专门写一整页食谱,他会随手撕一张便签,写上“蒜末 + 生抽 + 香油 + 醋 = 凉拌汁”。这只在当下有用,但它足够快速、足够直接——这就是 lambda 表达式的气质。

lambda 的完整语法只有一行:lambda 参数: 表达式。它不需要 def、不需要函数名、不需要 return,表达式的值自动成为返回值。例如 lambda x: x * 2 等价于 def f(x): return x * 2。

但 lambda 有着严格的自我限制:体内只能写一个表达式,不能包含赋值语句、多行逻辑或文档字符串。这让 lambda 天然适合那些简单、临时、定义完马上用掉的场合。一旦逻辑变复杂,就说明该用 def 写一道正式食谱了。

用 lambda 就像用便签:写的时候痛快,但如果你发现这张便签开始承担复杂的流程,就该把它誊写为正式食谱。判断标准永远是“可读性优先”。

λx:expr⟺def f(x): return expr\lambda x: \mathrm{expr} \quad\Longleftrightarrow\quad \mathrm{def}\ f(x){:}\ \mathrm{return}\ \mathrm{expr}
这个公式说明了 lambda 与普通函数在“输入 → 输出”层面完全等价,只是 lambda 省去了名字和 return,只能承载单行表达式。
  1. lambda 关键字声明一个匿名函数,它是“便签纸”的标题,不是正式食谱封面。
    λ\lambda
  2. 参数 x与 def 的参数列表一样,可以是位置参数、默认参数甚至 *args,多个参数用逗号分隔。
    xx
  3. 冒号与表达式 expr冒号后面只能有一个求值表达式,不能有赋值等语句;表达式的结果就是返回值。
    : expr{:}\ \mathrm{expr}
  4. 与 def 的等价关系理论上一对一映射,但 def 能承载更多行逻辑、文档和语句。
    ⟺def f(x): return expr\Longleftrightarrow \mathrm{def}\ f(x){:}\ \mathrm{return}\ \mathrm{expr}

lambda高阶应用

便签食谱最常见的用法,是把它传递给其他工具。Python 的 sorted 函数接受一个名为 key 的形参,用来指定“按什么排序”——你给出一张便签,sorted 就会按便签上的规则把食材排好队。

例如 sorted(dishes, key=lambda d: d["time"]),会按每道菜的耗时从小到大排列。key 参数接收一个“把一个元素变成排序依据”的函数,lambda 在这里几乎是最自然的选择。类似的还有 map(lambda x: x * 2, data) 对每个元素做变换,filter(lambda x: x > 0, data) 筛选元素。

但这里有一个著名陷阱:在循环中创建 lambda 并立刻引用循环变量,会因为闭包捕获了变量本身(而非当时的取值)而导致所有 lambda 最终看到同一个值。比如 [lambda: i for i in range(3)] 这三个函数都返回 2。解决方法是给 lambda 绑定默认参数 lambda i=i: i,把当前值“快照”进便签。

lambda 是把双刃剑:用对了是高效调味,滥用则让代码变成无人能懂的乱码。高阶 Python 的成熟度不在于“用了多酷的语法”,而在于知道何时该用、何时该克制。

sorted(data, key=λv: v[1])\mathrm{sorted}(\mathrm{data},\ \mathrm{key}=\lambda v{:}\ v[1])
这个公式是“按元素的第 1 个字段排序”的经典写法。key 参数接收的 lambda 是排序的比较依据,不会修改原数据。
  1. sorted 与 keysorted 按 key 函数返回的值对元素排序,key 只读不改。
    sorted(data, key)\mathrm{sorted}(\mathrm{data},\ \mathrm{key})
  2. lambda 提取排序依据对每个元素 v 提取其索引 1 处的字段(如耗时、得分),作为排序依据。
    λv: v[1]\lambda v{:}\ v[1]
  3. 捕获陷阱与修复循环内直接用循环变量时,lambda 捕获的是引用而非当时的取值;绑定默认参数 i=i 可解除。
    λi=i: i\lambda i=i{:}\ i

AI 应用场景

数据清洗流水线中的字段映射

阶段:数据预处理与特征工程

解决的问题:原始日志里的字段经常带着单位、脏字符,比如价格字段是 "¥12.5"、温度字段是 "37°C"。传统做法是写大量重复的 for 循环逐个清理,代码冗长且易错。用函数封装“清洗规则”,再配合 lambda 把规则套用到每个字段,一次封装处处复用。

具体案例:某风控团队的 AI 工程师从交易日志中提取特征,写了 transform_column(data, func) 函数,调用时用 lambda x: float(x.replace("¥", "")) 批量转换价格字段,用 lambda x: x.lower().strip() 清洗地址字符串,原本 200 行的清洗脚本缩减到 30 行。

模型评估中的候选排序

阶段:模型训练与调参评估

解决的问题:训练完多个候选模型,需要按 AUC、F1、耗时等多个指标综合排序。传统方式要写多重嵌套排序逻辑,可读性差。使用 sorted(models, key=lambda m: m["f1"], reverse=True) 一行完成按指标排序,还能快速切换排序字段。

具体案例:在 NLP 分类任务中,一位算法工程师对 5 个候选模型做了交叉验证,用 key=lambda m: (m["f1"], -m["latency_ms"]) 实现“先按 F1 降序、再按时延升序”的复合排序,快速锁定了最终上线的模型。

指标计数器的闭包缓存

阶段:推理服务与监控告警

解决的问题:线上模型推理往往需要统计某个特征被触发的次数,传统做法是引入类或全局变量,代码冗余。闭包可以把“计数状态”和“计数行为”封装在一起,就像给监控系统加了一台自带计数功能的仪表。

具体案例:一个推荐系统团队需要统计每小时各推荐策略的曝光次数,用 make_counter() 闭包工厂生成独立计数器,每个策略各持有一只“便当盒”,既灵活又不污染全局命名空间,配合定时器上报给监控平台。

Python 演示

python48 行
"""食谱厨房:函数与lambda实战演示"""  def make_dish(name, *ingredients, cooking_time=10, **options):    """把食材列表变成一道菜的字典。"""    if not ingredients:        raise ValueError(f"{name} 缺少食材,无法下锅")    dish = {"name": name, "ingredients": list(ingredients), "time": cooking_time}    dish.update(options)    return dish  def make_sauce(flavor):    """闭包:生成一个固定风味的调味函数。"""    def sauce():        return f"{flavor}味酱汁"    return sauce  def main():    # 1. 位置参数 + 默认参数 + 关键字参数    dish1 = make_dish("番茄炒蛋", "番茄", "鸡蛋", cooking_time=8, chef="小王")    print(dish1)     # 2. 可变参数把零散食材收进元组    dish2 = make_dish("大杂烩", "土豆", "牛肉", "胡萝卜", "洋葱", cooking_time=25)    print(dish2)     # 3. 闭包:走到哪里都带着秘制酱汁    secret = make_sauce("花椒")    print(secret())     # 4. lambda 作为排序规则    menu = [        {"name": "番茄炒蛋", "time": 8},        {"name": "红烧肉", "time": 40},        {"name": "拍黄瓜", "time": 3},    ]    quickest = sorted(menu, key=lambda d: d["time"])    print("最快上菜:", [d["name"] for d in quickest])     # 5. lambda 捕获陷阱与修复    funcs = [lambda i=i: i for i in range(3)]    print("快照修复:", [f() for f in funcs])  if __name__ == "__main__":    main()
目的:用“食谱厨房”场景串起函数定义、可变参数、闭包和 lambda 排序,演示如何在实际项目中用函数与 lambda 写出简洁可复用的代码。
  1. def make_dish(name, *ingredients, cooking_time=10, **options):一行同时声明位置参数、可变参数、默认参数和关键字参数,覆盖全部传参方式。
  2. if not ingredients:判断食材元组是否为空,缺少食材直接抛出异常,体现“早退”防御式编程。
  3. dish.update(options)把 chef 等额外关键字参数合并进结果字典,**options 大显身手。
  4. def make_sauce(flavor):外层函数,flavor 将成为闭包捕获的自由变量。
  5. return sauce返回内层函数本身,让“便当盒”被带到函数外部。
  6. secret = make_sauce("花椒")拿到一个自带花椒风味的函数,闭包状态随身携带。
  7. quickest = sorted(menu, key=lambda d: d["time"])用 lambda 提取排序关键字,一行完成按耗时排序。
  8. funcs = [lambda i=i: i for i in range(3)]绑定默认参数把循环变量“快照”,演示修复闭包捕获陷阱的标准写法。

常见误区

误区很多初学者写完 `def` 缩进块,就觉得代码“跑了”,结果程序没有一点输出,还以为自己写错了。

正解def 只是登记了一张食谱卡,把代码对象绑定到函数名上。只有加括号调用 函数名(),函数体才会真正执行。就像食谱卡放在抽屉里并不会自动做菜,只有你拿出来照做才行。先区分“定义”与“调用”,是学会函数的第一课。

为何常见:平时写脚本都是从上到下立即执行,遇到“定义”和“调用”两个概念的分离,直觉上容易混淆,误以为缩进块本身就是执行信号。

误区总以为 `*args` 里的 `args` 是关键字,必须叫这个名字才能生效。

正解* 和 ` 才是语法核心,后面的名字只是变量名。*items、options` 完全一样有效,但要保证同一个函数里形参名不冲突。就像说“任意数量的食材”,袋子本身叫什么不重要,重要的是那个星号表明“把这些东西打包”。

为何常见:教程里到处用 *args/kwargs,看得多了自然就以为名字是硬性规定,而忽略了真正的语法标记是星号。

误区在函数里写 `count += 1`,就以为修改了外面的全局 `count`。

正解对变量赋值时,Python 默认把该变量看作局部变量;要修改全局变量本身必须声明 global。否则会因为“先赋值后使用”的逻辑报 UnboundLocalError。这就像在厨房工作台旁新放了一袋米,不等于移动了储藏室里那袋米——赋值动作默认是在“当前领地”里新建名字。

为何常见:“离得最近的那个变量”这个直觉在赋值语义下失效,很多人没意识到赋值本身就是“创建新名字”的动作,而非纯粹地修改旧东西。

误区觉得 lambda 写起来更短更酷,于是所有函数都用 lambda,结果代码变得难以阅读和维护。

正解lambda 只能包含一个表达式,不支持赋值语句、多行逻辑和文档字符串。一旦逻辑超过一行,可读性急剧下降,调试也困难。正式食谱该用 def,便签纸不该用来写满汉全席。工程上更看重“团队能看懂”,而不是“一行写完”。

为何常见:演示代码里 lambda 极其简洁、很有冲击力,容易让人高估它的能力边界,忽略了它“只适合短小临时逻辑”的本质。

误区以为 `sorted` 会像 `list.sort()` 一样原地修改原列表,于是后续数据被弄乱,引发一系列隐蔽 bug。

正解sorted 返回一个全新的排序后列表,原列表保持不动;list.sort() 才原地修改。lambda 只是告诉 sorted 按什么标准比较,不会改写数据。这就像你照食谱重新抄了一份清单,原来的冰箱并没有变。需要原地排序时再用 list.sort()。

为何常见:sorted 和 sort 名字极像,很多入门资料把二者混用,导致功能误记。二者一个“返回新菜”,一个“重炒旧菜”。

面试题

Q1. 关于 Python 函数的 def 定义,下列说法最准确的是?

单选题 · easy

  • A.def 语句执行时会立刻运行函数体
  • B.def 仅定义函数对象,调用时才执行← 正确答案
  • C.函数可以凭空使用,不依赖任何定义
  • D.def 必须放在文件最开头才能使用

答案:B。def 只是把函数体打包成函数对象并绑定到名称上,并不会立即执行函数体。只有加括号调用 f() 时才会执行——就像把食谱卡放进抽屉并不等于菜做好了。A 混淆了定义和调用;C 完全违背函数必须先定义后使用的原则;D 中 def 可以出现在任何位置,只是调用时要求已经定义。

Q2. 关于默认参数 b=1,下列哪种说法正确?

单选题 · mid

  • A.每次调用函数时都要重新计算默认值
  • B.默认值在函数定义时便已经固定下来← 正确答案
  • C.默认值只在第一次调用时计算出结果
  • D.默认值在程序运行结束时才进行求值

答案:B。Python 在函数定义时就会对默认参数求值并固定下来,后续调用都复用同一个对象。这就像食谱卡上的“默认放两勺糖”在写卡时就定了,不会每次做菜重新决定。因此默认参数不能使用可变对象(如列表),否则会跨调用共享,产生“上次客人留下的食材污染下次菜”的诡异 bug。

Q3. 执行 def f(a, b, *args): pass,再调用 f(1, 2, 3, 4),args 的值是?

单选题 · easy

  • A.一个包含 3 和 4 的元组对象← 正确答案
  • B.一个包含 3 和 4 的列表对象
  • C.一个包含 3 和 4 的集合对象
  • D.两个彼此独立的普通参数对象

答案:A。*args 会把多出来的位置参数打包成元组(tuple)。a=1, b=2 被前两个形参接收,剩下的 3, 4 进入 args,即 args=(3, 4)。它不是列表、不是集合,也不是两个独立参数,而是“一个装了两颗食材的保鲜袋”。如果换成 kwargs,则是打包成字典。

Q4. 在函数内部查找一个变量时,Python 按什么顺序搜索各作用域?

单选题 · mid

  • A.局部到外层再到全局再到内置← 正确答案
  • B.全局再到局部再到内置再到外层
  • C.内置到外层再到全局再到局部
  • D.外层到局部再到内置再到全局

答案:A。LEGB 规则即 Local → Enclosing → Global → Builtin,由内向外逐层查找,先找到谁就用谁。就像厨师先看灶台边调料架(局部),再看隔壁备用柜(嵌套),然后去储藏室(全局),最后用整栋楼的基础水电(内置)。其余三个选项的次序都不符合 Python 规定。

Q5. 关于闭包,下列说法正确的是?

多选题 · mid

  • A.闭包可以捕获外层函数的局部变量← 正确答案
  • B.闭包可直接修改外层变量而无需声明
  • C.闭包由内层函数引用外层变量构成← 正确答案
  • D.闭包变量在外层函数结束后全部遗失

答案:AC。闭包的核心是内层函数引用了外层函数的局部变量(自由变量),并且外层函数将这些变量“打包”在返回的函数里,因此 A、C 正确。B 错误:要修改自由变量必须 nonlocal 声明,否则会在内层新建局部变量。D 错误:闭包的标志性能力恰恰是外层函数结束后,自由变量依然存活。这就像带着便当盒离开厨房后,秘制调料还在手边。

Q6. lambda 表达式体内不能包含下列哪一项?

单选题 · easy

  • A.简单的四则算术运算表达式
  • B.三元条件表达式这种单行写法
  • C.赋值语句这类有副作用的语句← 正确答案
  • D.对其它已定义函数的调用语句

答案:C。lambda 体内的语法限制是“只能有一个表达式”,因此赋值语句(如 x = 1)、多行逻辑或 return 都不能出现。代码 lambda x: x = 1 会直接语法报错。算术、三元表达式、函数调用本质上都是“求出某个值”的表达式,所以允许。这就像便签纸只能写一行配方,写不了多步骤操作手册。

Q7. 执行 sorted([('a', 3), ('b', 1)], key=lambda x: x[1]) 的结果是?

单选题 · mid

  • A.列表保持原顺序 [('a', 3), ('b', 1)]
  • B.变成 [('b', 1), ('a', 3)] 按第二项升序← 正确答案
  • C.原列表被原地修改并按第二项排序
  • D.抛出 TypeError 参数类型不匹配错误

答案:B。sorted 的 key 参数指定排序依据,lambda x: x[1] 让每个元组按索引 1 处的值比较,因此 1 排前面,3 排后面。同时 sorted 返回新列表,原列表不变,所以 C 错误。A 是原顺序,D 是莫须有的错误——lambda 在这里完全合法。

Q8. 关于函数返回值,下列说法正确的有?

多选题 · hard

  • A.没有 return 语句时函数返回 None← 正确答案
  • B.return 多个值实际返回一个元组对象← 正确答案
  • C.return 之后的代码仍然会被继续执行
  • D.返回值数量完全由函数的调用方决定

答案:AB。没有 return 时函数自动返回 None,A 正确。return x, y 的逗号会构造元组,B 正确。return 一旦执行,函数立即结束,后面的代码不会运行,C 错误。返回值数量由函数定义与 return 语句决定,不由调用方决定,D 错误。这就像菜端出厨房后不会回锅续炒,上几道菜由后厨说了算。

Q9. 关于 kwargs 可变参数,下列哪项描述最准确?

单选题 · mid

  • A.接收位置参数并打包成一个元组对象
  • B.接收关键字参数并打包成一个字典对象← 正确答案
  • C.接收列表参数并把列表展开成单个元素
  • D.只能用于 lambda 表达式的简单参数场景

答案:B。kwargs 专门收集调用时多出来的关键字参数,打包成字典(dict)。比如 f(a=1, salt='少许') 中 kwargs={'salt': '少许'}。A 描述的是 *args 的行为;C 中“展开列表”是调用侧的 *list 语法,与 kwargs 无关;D 中 lambda 也支持 kwargs,但并非“只能用于”。

Q10. 关于函数与 lambda 的关系和特性,下列哪些说法正确?

多选题 · hard

  • A.lambda 适用于简单单表达式的临时场景← 正确答案
  • B.lambda 能够拥有自己的独立文档字符串
  • C.函数是一等对象,可以赋值给变量使用← 正确答案
  • D.函数体内部还可以继续定义新的函数← 正确答案

答案:ACD。lambda 因为只能写单表达式,所以适合临时小逻辑,A 正确。函数在 Python 中是一等对象,可作为参数传递、赋值给变量、装进列表,C 正确。函数体内可以嵌套定义新函数,这种结构是闭包的基础,D 正确。B 错误:lambda 没有独立的文档字符串语法,它只是一个匿名表达式,无法像 def 那样用 docstring 说明用途——便签纸写不下使用说明书。