匿名函数
匿名函数是没有通过 def 命名的函数,Python 使用 lambda 表达式创建它。它适合把一条很短的计算或判断直接传给 sorted()、map()、filter() 等高阶函数。
本文使用 Python 3.10 及以上版本。读完后,你应该能够正确书写 lambda,理解它只能包含一个表达式,并在匿名函数和命名函数之间做出可维护的选择。
基本语法
lambda 参数1, 参数2, ...: 表达式
调用 lambda 得到的仍是普通函数对象:
add = lambda left, right: left + right
print(add(2, 3)) # 5
print(add.__name__) # <lambda>
冒号右侧必须是一个表达式,它会自动作为返回值,不能在其中直接写 return、赋值语句、for 循环或多条独立语句:
is_even = lambda number: number % 2 == 0
print(is_even(4)) # True
表达式可以使用条件表达式、函数调用或容器字面量,但不要为了压缩代码而塞入过多逻辑:
label = lambda score: "及格" if score >= 60 else "不及格"
print(label(75)) # 及格
作为回调传入高阶函数
匿名函数最常见的用途是“只使用一次的短函数”。例如按字符串长度排序:
words = ["Python", "Go", "JavaScript", "C"]
print(sorted(words, key=lambda word: len(word)))
# ['C', 'Go', 'Python', 'JavaScript']
也可以把转换和筛选规则写在调用位置:
numbers = [1, 2, 3, 4]
squares = list(map(lambda number: number * number, numbers))
odds = list(filter(lambda number: number % 2 == 1, numbers))
print(squares) # [1, 4, 9, 16]
print(odds) # [1, 3]
map() 和 filter() 在 Python 3 中返回惰性迭代器,所以示例使用 list() 物化结果。数据处理流程较长时,可以把每一步写成命名函数或列表推导式,使中间意图更明显。
参数写法
lambda 的参数规则与 def 基本相同,可以使用默认参数、关键字参数、可变位置参数和可变关键字参数:
format_name = lambda first, last="": f"{last}{first}"
print(format_name("小林")) # 小林
print(format_name("小林", "陈")) # 陈小林
collect = lambda *values, **options: (values, options)
print(collect(1, 2, unit="kg")) # ((1, 2), {'unit': 'kg'})
参数过多会让匿名函数难以阅读,也不利于类型检查;这时应改为 def 并添加类型提示和文档字符串。
用表达式替代简单分支
匿名函数不能写普通的 if 语句,但可以写条件表达式:
classify = lambda value: "正数" if value > 0 else "非正数"
不要把多个条件嵌套成难以理解的一行:
# 虽然合法,但不易维护
grade = lambda score: "A" if score >= 90 else "B" if score >= 80 else "C"
如果规则会增长,使用普通函数更合适:
def grade(score):
if score >= 90:
return "A"
if score >= 80:
return "B"
return "C"
闭包与晚绑定
lambda 同样可以捕获外层变量,因此也会遇到闭包的晚绑定问题:
def make_multipliers():
return [lambda value: value * factor for factor in [1, 2, 3]]
functions = make_multipliers()
print([function(10) for function in functions]) # [30, 30, 30]
调用发生时,三个函数都读取到最后的 factor=3。使用默认参数把当前值绑定下来:
def make_multipliers():
return [
lambda value, factor=factor: value * factor
for factor in [1, 2, 3]
]
print([function(10) for function in make_multipliers()]) # [10, 20, 30]
这种默认参数不是给调用者准备的业务参数,而是创建函数时保存状态的常用技巧。若团队成员不熟悉该写法,改用内部命名函数通常更直观。
与 def 的比较
| 场景 | lambda | def |
|---|---|---|
| 一次性、单表达式回调 | 简洁 | 稍显冗长 |
| 需要复用或单元测试 | 名称为 <lambda>,不利于定位 | 有明确名称,推荐 |
| 多条语句、循环、异常处理 | 不支持 | 支持 |
| 类型提示和文档字符串 | 可以写注解但可读性较差 | 更清晰、完整 |
| 调试和日志 | 栈信息不够有意义 | 函数名便于追踪 |
lambda 并不比 def 更快,也不是“更函数式”的必选写法。选择标准应是逻辑短且放在调用点后仍然容易读懂。
常见错误
在 lambda 中写语句
# 错误:lambda 不能包含赋值语句和 return
# lambda number: total = number * 2
# lambda number: return number * 2
把多步处理拆成命名函数:
def double(number):
result = number * 2
return result
误把函数调用写进回调参数
numbers = [1, 2, 3]
# 错误:double(2) 会先执行,得到整数,map 需要的是函数
# map(double(2), numbers)
print(list(map(lambda number: number * 2, numbers))) # [2, 4, 6]
忘记 lambda 返回的是函数
make_adder = lambda amount: lambda value: value + amount
add_five = make_adder(5)
print(add_five(3)) # 8
这里第一次调用得到新的函数,第二次调用才计算结果。嵌套 lambda 虽然合法,但通常应该改写成 def 以提升可读性。
练习
- 使用
lambda按字典中score字段对学生列表排序。 - 使用
map()把字符串列表转换为去除空白后的大写字符串。 - 使用
filter()筛选出长度不小于 5 的单词。 - 将一个包含多个条件的
lambda改写为有名称的函数,并说明改写理由。
小结
lambda用于创建匿名函数,语法是lambda 参数: 表达式。- 它只能包含一个表达式,表达式的值会自动返回。
- 一次性、短小的回调很适合使用
lambda;复杂、复用或需要调试的逻辑应使用def。 lambda也会捕获外部变量,循环中要注意晚绑定,可用默认参数固定当前值。- 匿名函数和命名函数行为相同,主要差别在可读性、调试体验和维护成本。