跳到主要内容

匿名函数

匿名函数是没有通过 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 的比较​

场景lambdadef
一次性、单表达式回调简洁稍显冗长
需要复用或单元测试名称为 <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 以提升可读性。

练习​

  1. 使用 lambda 按字典中 score 字段对学生列表排序。
  2. 使用 map() 把字符串列表转换为去除空白后的大写字符串。
  3. 使用 filter() 筛选出长度不小于 5 的单词。
  4. 将一个包含多个条件的 lambda 改写为有名称的函数,并说明改写理由。

小结​

  • lambda 用于创建匿名函数,语法是 lambda 参数: 表达式。
  • 它只能包含一个表达式,表达式的值会自动返回。
  • 一次性、短小的回调很适合使用 lambda;复杂、复用或需要调试的逻辑应使用 def。
  • lambda 也会捕获外部变量,循环中要注意晚绑定,可用默认参数固定当前值。
  • 匿名函数和命名函数行为相同,主要差别在可读性、调试体验和维护成本。