K 的一隅

Python Python 语言核心

lambda 只能装一条表达式:什么时候该用、什么时候别用

讲清 lambda 的语法边界——只能是表达式、不能含语句,以及和 sorted 的 key 配合;何时一行够用、何时该写 def。

7 分钟阅读 更新于 2026-07-28
本文目录

lambda 能写的,比很多人以为的少:有且仅有一条表达式,没有 return 关键字,也没有 if 语句块、for 循环、while 或赋值语句。它产出的仍是一个普通函数对象——可以传给 sorted、可以赋给变量、也受 LEGB 约束——只是没有名字、体积极小。

边界清楚了,就不会用它硬塞本该用 def 写的逻辑;也不会在需要语句的地方写出语法错误。

lambda 出现在 Python 里很早,但语义并不“特殊”:它只是 def 的表达式形式,产生同样的 function 对象类型。区别主要在语法限制和是否有 __name__(lambda 通常是 <lambda>)。

语法:参数列表 => 表达式

python
double = lambda x: x * 2
print(double(5))  # 10

add = lambda a, b: a + b
print(add(1, 2))  # 3

语义上等价于带 returndef

python
def double(x: int) -> int:
    return x * 2

lambda 本身就是表达式:可以出现在字典值、列表元素、函数调用实参里。def 是语句,必须单独占一行(或出现在复合语句块里)。

表达式之内:条件表达式可以,语句不行

Python 的三元条件 a if cond else b 是表达式,可以嵌在 lambda 里:

python
sign = lambda n: 0 if n == 0 else (1 if n > 0 else -1)
print(sign(-3))  # -1

下面这些都不行——if/for/try 是语句;x = 1 赋值也是语句:

python
# 语法错误示意
# bad = lambda x: if x > 0: return x
# bad = lambda x: (x = x + 1)

需要中间变量、多步计算、异常处理时,换 def,起可读的名字。

和 sorted 的 key:最典型的合适场景

sortedlist.sortkey 参数接受一个单参数 callable:比较前对每个元素调用一次,用返回值排序。规则简单、只用一次、体只是一条表达式时,lambda 很顺手:

python
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)

字典列表按字段排:

python
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"])  # Ada

reverse=True 只影响升降序,不改变 key 的语义:key 仍是对每个元素提取“排序键”的表达式结果。

多级排序可以返回元组作 key——仍是一条表达式,lambda 能写,但可读性开始下降:

python
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 规则:

python
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) 对每项调用 fnfilter(pred, iterable) 保留 pred 为真的项。两者都接受 callable,lambda 常用于一次性的小转换:

python
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:对照表

lambdadef
形式表达式语句
函数体单条表达式任意语句块
名字匿名(可赋给变量)定义时绑定名字
标注不支持标准参数标注支持
__doc__通常无可用 docstring

PEP 8 的风格建议是:能用 def 清楚表达就别强行 lambda——尤其团队项目里,可读性常比少写四行字更重要。

作为实参内联:少起名,但别过度

lambda 常直接写在函数调用的括号里,省去中间名字:

python
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 并在里面下 printbreakpoint(),再传给 sorted

边界:何时该写 def

情况建议
超过一行或需要注释def,起有意义的名字
需要参数/返回值 type 标注def(lambda 不支持标准标注语法)
逻辑会复用、写单测、打日志def,便于引用和调试
需要多路径 return、循环、trydef
sorted / map / filter 的一次性短 keylambda 可以

循环里批量造 lambda 时,注意闭包捕获的是变量名,不是当时的值

python
# 常见陷阱:三个 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 包一层,语义更直观:

python
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))  # 20

lambda 与 LEGB

lambda 与 def 一样参与 LEGB:表达式里的自由变量从定义处的外层作用域查找。lambda 体内不能写 global / nonlocal(它们是语句):

python
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 / minkey、一次性的 map / filter 回调;不适合带语句、带标注、要复用或要调试的逻辑。与 LEGB 的关系和 def 相同——表达式里的自由变量从定义处外层读取;不能在 lambda 里写 global / nonlocal。团队代码里可以把 lambda 当作“只活一行”的 key 函数;业务规则、校验、持久化一律用有名字的 def。review 时若看到 lambda 里嵌套两个以上三元表达式,几乎总是该拆成具名函数的信号。标准库 functools.partial 有时比 lambda 更清楚地固定部分参数,那是另一种“少写参数列表”的工具,与 lambda 的表达式边界互补。记住表达式边界与可读性边界,就不容易滥用。系列下一篇进入类与对象,callable 规则会扩展到方法绑定与 self