本文目录
lambda 能写的,比很多人以为的少:有且仅有一条表达式,没有 return 关键字,也没有 if 语句块、for 循环、while 或赋值语句。它产出的仍是一个普通函数对象——可以传给 sorted、可以赋给变量、也受 LEGB 约束——只是没有名字、体积极小。
边界清楚了,就不会用它硬塞本该用 def 写的逻辑;也不会在需要语句的地方写出语法错误。
lambda 出现在 Python 里很早,但语义并不“特殊”:它只是 def 的表达式形式,产生同样的 function 对象类型。区别主要在语法限制和是否有 __name__(lambda 通常是 <lambda>)。
语法:参数列表 => 表达式
double = lambda x: x * 2
print(double(5)) # 10
add = lambda a, b: a + b
print(add(1, 2)) # 3语义上等价于带 return 的 def:
def double(x: int) -> int:
return x * 2lambda 本身就是表达式:可以出现在字典值、列表元素、函数调用实参里。def 是语句,必须单独占一行(或出现在复合语句块里)。
表达式之内:条件表达式可以,语句不行
Python 的三元条件 a if cond else b 是表达式,可以嵌在 lambda 里:
sign = lambda n: 0 if n == 0 else (1 if n > 0 else -1)
print(sign(-3)) # -1下面这些都不行——if/for/try 是语句;x = 1 赋值也是语句:
# 语法错误示意
# bad = lambda x: if x > 0: return x
# bad = lambda x: (x = x + 1)需要中间变量、多步计算、异常处理时,换 def,起可读的名字。
和 sorted 的 key:最典型的合适场景
sorted 与 list.sort 的 key 参数接受一个单参数 callable:比较前对每个元素调用一次,用返回值排序。规则简单、只用一次、体只是一条表达式时,lambda 很顺手:
words: list[str] = ["banana", "fig", "apple"]
by_len = sorted(words, key=lambda w: len(w))
print(by_len) # ['fig', 'apple', 'banana']
records: list[tuple[str, int]] = [("bob", 30), ("ada", 28), ("charlie", 30)]
by_age = sorted(records, key=lambda r: r[1])
print(by_age)字典列表按字段排:
users: list[dict[str, int | str]] = [
{"name": "Ada", "score": 92},
{"name": "Bob", "score": 88},
]
ranked = sorted(users, key=lambda u: u["score"], reverse=True)
print(ranked[0]["name"]) # Adareverse=True 只影响升降序,不改变 key 的语义:key 仍是对每个元素提取“排序键”的表达式结果。
多级排序可以返回元组作 key——仍是一条表达式,lambda 能写,但可读性开始下降:
people: list[tuple[str, int, str]] = [
("Ada", 28, "B"),
("Bob", 30, "A"),
("Cara", 28, "A"),
]
by_age_grade = sorted(people, key=lambda p: (p[1], p[2]))
print(by_age_grade[0]) # ('Cara', 28, 'A')先比年龄再比等级。若这种 key 会出现三次以上,抽成 def sort_key(person: tuple[str, int, str]) -> tuple[int, str]: ... 通常更划算。
max / min 也接受 key,与 sorted 同一套 callable 规则:
words: list[str] = ["fig", "banana", "apple"]
longest = max(words, key=lambda w: len(w))
print(longest) # banana若 key 逻辑会复用,或需要类型标注,用 def 往往更清晰——上一篇里 by_length 的例子就是这种写法。标准库 operator.itemgetter(1) 也能替代 lambda r: r[1],性能略好、名字更短,属于同一类工具选择。
map、filter 与 lambda
map(fn, iterable) 对每项调用 fn;filter(pred, iterable) 保留 pred 为真的项。两者都接受 callable,lambda 常用于一次性的小转换:
nums: list[int] = [1, 2, 3, 4]
evens = list(filter(lambda n: n % 2 == 0, nums))
squares = list(map(lambda n: n * n, nums))
print(evens, squares) # [2, 4] [1, 4, 9, 16]列表推导式 [n * n for n in nums] 在多数场景更可读;lambda + map/filter 在要把函数当实参传递时仍有用。
lambda 与 def:对照表
lambda | def | |
|---|---|---|
| 形式 | 表达式 | 语句 |
| 函数体 | 单条表达式 | 任意语句块 |
| 名字 | 匿名(可赋给变量) | 定义时绑定名字 |
| 标注 | 不支持标准参数标注 | 支持 |
__doc__ | 通常无 | 可用 docstring |
PEP 8 的风格建议是:能用 def 清楚表达就别强行 lambda——尤其团队项目里,可读性常比少写四行字更重要。
作为实参内联:少起名,但别过度
lambda 常直接写在函数调用的括号里,省去中间名字:
pairs: list[tuple[int, int]] = [(3, 1), (1, 2), (2, 2)]
pairs.sort(key=lambda p: (p[0], p[1]))
print(pairs) # [(1, 2), (2, 2), (3, 1)]嵌套过深时可读性急剧下降。经验法则:若 lambda 超过约 40 个字符、或需要嵌套三元表达式,抽 def 并在下一行调用。代码审查里“看不懂 key 在算什么”的抱怨,多半来自内联 lambda 过长,而不是 sorted 本身难。
另一种边界是副作用:lambda 体只能是表达式,不能写 print 调试(print 虽是函数调用表达式,但把调试塞进 lambda 会让 key 函数带隐蔽 IO)。需要日志或断点时,用 def 并在里面下 print 或 breakpoint(),再传给 sorted。
边界:何时该写 def
| 情况 | 建议 |
|---|---|
| 超过一行或需要注释 | def,起有意义的名字 |
| 需要参数/返回值 type 标注 | def(lambda 不支持标准标注语法) |
| 逻辑会复用、写单测、打日志 | def,便于引用和调试 |
| 需要多路径 return、循环、try | def |
sorted / map / filter 的一次性短 key | lambda 可以 |
循环里批量造 lambda 时,注意闭包捕获的是变量名,不是当时的值:
# 常见陷阱:三个 lambda 可能都看到最终的 n
funcs_bad = [lambda x: x * n for n in range(3)]
# 用默认参数把“当前 n”冻结进每个 lambda
funcs_ok = [lambda x, n=n: x * n for n in range(3)]
print(funcs_ok[2](10)) # 20或用工厂 def 包一层,语义更直观:
def make_multiplier(n: int) -> callable[[int], int]:
return lambda x: x * n
mults = [make_multiplier(n) for n in range(3)]
print(mults[2](10)) # 20lambda 与 LEGB
lambda 与 def 一样参与 LEGB:表达式里的自由变量从定义处的外层作用域查找。lambda 体内不能写 global / nonlocal(它们是语句):
factor: int = 10
scale = lambda x: x * factor
print(scale(3)) # 30若在外层函数里需要改 Enclosing 变量,应在外层用 def + nonlocal,不要把赋值塞进 lambda。
常见误解
| 误解 | 实际 |
|---|---|
| lambda 比 def 更快 | 同是 function 对象,无本质性能差 |
| lambda 可以写多行 | 只能一条表达式;复杂逻辑用 def |
| lambda 是特殊内置 | 就是创建 function 对象的表达式 |
| sorted 必须用 lambda | 任何单参数 callable 都行 |
lambda 里能写 return | 不需要也不能写;表达式值即返回值 |
记住两条:lambda = 匿名 + 单表达式;稍复杂就 def。在 sorted(..., key=lambda ...) 里它最常用;一旦你要向同事解释“这段 key 到底怎么算”,有名字的函数通常更好维护。
再往后,装饰器会把 def 包一层又一层——那是语法糖叠在 callable 与 LEGB 规则之上。lambda 不会出现在装饰器定义里(装饰器本身要用 def),但 key=、map 里仍会反复见到它;知道边界,就不会误把 lambda 当成“简化版 def”去写业务逻辑。
和 JavaScript 箭头函数不同,Python 的 lambda 不能包含语句块,也没有“隐式 return 多条语句”的扩展。若你从 JS 转来,最容易写的错误就是把多行逻辑塞进 lambda;看到语法报错,第一反应应是改成 def,而不是找绕过的写法。
小结
lambda 产出普通 function 对象,但语法上只能是单条表达式:适合 sorted / max / min 的 key、一次性的 map / filter 回调;不适合带语句、带标注、要复用或要调试的逻辑。与 LEGB 的关系和 def 相同——表达式里的自由变量从定义处外层读取;不能在 lambda 里写 global / nonlocal。团队代码里可以把 lambda 当作“只活一行”的 key 函数;业务规则、校验、持久化一律用有名字的 def。review 时若看到 lambda 里嵌套两个以上三元表达式,几乎总是该拆成具名函数的信号。标准库 functools.partial 有时比 lambda 更清楚地固定部分参数,那是另一种“少写参数列表”的工具,与 lambda 的表达式边界互补。记住表达式边界与可读性边界,就不容易滥用。系列下一篇进入类与对象,callable 规则会扩展到方法绑定与 self。