匿名函数 lambda 与高阶函数

Python 支持函数式编程的核心武器有两个:lambda(匿名函数)和高阶函数(把函数当参数或返回值的函数)。前面 函数定义与调用 里说过”函数是一等公民”,这一篇就把这条规则的用法讲透——lambdamapfilterreducesortedkeyfunctools.partial,用完之后你的代码会明显变短、变优雅。

一、lambda 语法

lambda 定义一个匿名的、只能写一个表达式的函数:

1
2
3
4
5
6
7
8
# 传统函数
def double(x):
return x * 2

# lambda
double = lambda x: x * 2

print(double(5)) # 10

结构:lambda 参数列表: 表达式。没有 return,表达式的值就是返回值。

限制

  • 只能一个表达式,不能有语句if x: return y 不行);
  • 不能有 return
  • 不能有多行;
  • 匿名——没有名字(虽然可以赋给变量)。

多参数:

1
2
3
4
5
add = lambda a, b: a + b
add(3, 4) # 7

# 默认参数、*args、**kwargs 也都支持
f = lambda x, y=10, *args, **kwargs: (x, y, args, kwargs)

二、什么时候用 lambda

lambda 的唯一价值是”传给别的函数当参数”——用完即扔。

1
2
3
4
5
6
7
8
# 排序:按字符串长度
sorted(["banana", "apple", "cherry"], key=lambda s: len(s))

# 过滤:只要正数
list(filter(lambda x: x > 0, [-2, -1, 0, 1, 2]))

# 映射:加一
list(map(lambda x: x + 1, range(5)))

不建议给 lambda 起名

1
2
3
4
5
6
# ❌ 反面教材:赋名字给 lambda
double = lambda x: x * 2

# ✅ 直接 def 更好
def double(x):
return x * 2

Guido 本人和 PEP 8 都认为——如果 lambda 需要名字,那你应该用 def。

三、高阶函数:函数作为参数

Python 内置了几个经典高阶函数:mapfilterreducesortedmin/maxany/all

map(func, iter)

对每个元素调用 func:

1
2
3
4
5
6
7
list(map(str.upper, ["a", "b", "c"]))       # ['A', 'B', 'C']
list(map(len, ["hi", "hello", "world"])) # [2, 5, 5]
list(map(lambda x: x*2, [1, 2, 3])) # [2, 4, 6]

# 多个可迭代对象:并行取
list(map(lambda a, b: a + b, [1, 2, 3], [10, 20, 30]))
# [11, 22, 33]

filter(pred, iter)

保留返回真的元素:

1
2
3
4
5
list(filter(lambda x: x > 0, [-2, -1, 0, 1, 2]))     # [1, 2]
list(filter(str.isalpha, ["abc", "12", "hi"])) # ['abc', 'hi']

# pred 为 None 时相当于用 bool
list(filter(None, [0, 1, "", "hi", []])) # [1, 'hi']

reduce(func, iter, initial)

累积计算,需要从 functools 导入:

1
2
3
4
5
6
7
8
9
10
11
from functools import reduce

# 求和:等价 sum([1, 2, 3, 4])
reduce(lambda a, b: a + b, [1, 2, 3, 4]) # 10
reduce(lambda a, b: a + b, [1, 2, 3, 4], 100) # 110(带初始值)

# 找最大:等价 max
reduce(lambda a, b: a if a > b else b, nums)

# 拼字符串
reduce(lambda a, b: a + b, ["hi ", "there ", "friend"])

reduce 曾经是 Python 内置的,Python 3 里移到了 functools,Guido 认为大部分场景用推导式或者 sum/min/max 就够,不需要 reduce。

map/filter vs 推导式

现代 Python 更推荐列表推导式

1
2
3
4
5
# map + filter
list(map(lambda x: x*2, filter(lambda x: x > 0, nums)))

# 推导式(更清晰)
[x*2 for x in nums if x > 0]

推导式一般更好读。map/filter 适合”已有函数不用 lambda”的场景:

1
2
3
4
5
# ✅ 简洁
list(map(str.upper, words))

# 推导式反而多打几个字
[w.upper() for w in words]

四、sorted / min / max 的 key

高阶函数最常见的用法:给排序函数传 key。

1
2
3
4
5
6
7
8
9
10
11
words = ["Banana", "apple", "cherry"]
sorted(words) # ['Banana', 'apple', 'cherry'] 按 ASCII
sorted(words, key=str.lower) # ['apple', 'Banana', 'cherry'] 忽略大小写
sorted(words, key=len) # 按长度

people = [("张三", 25), ("李四", 20), ("王五", 30)]
sorted(people, key=lambda p: p[1]) # 按年龄
sorted(people, key=lambda p: -p[1]) # 按年龄降序

# 多字段:先按第一个,再按第二个
records.sort(key=lambda r: (r.grade, r.name))

operator 更快:

1
2
3
4
5
from operator import itemgetter, attrgetter

sorted(people, key=itemgetter(1)) # 按第 1 项(年龄)
sorted(users, key=attrgetter("age")) # 按 .age 属性
sorted(data, key=itemgetter("category", "price")) # 多 key

itemgetter/attrgetter 是 C 实现,比 lambda 快很多,代码也更清晰。

五、functools.partial:偏函数

partial 固定函数的一部分参数,产生新函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import partial

def power(base, exp):
return base ** exp

square = partial(power, exp=2) # 固定 exp=2
cube = partial(power, exp=3)

print(square(5)) # 25
print(cube(3)) # 27

# 也可以固定位置参数
add_10 = partial(lambda a, b: a + b, 10)
print(add_10(5)) # 15

用途:创建”预配置”的函数。特别适合传给 map/filter/sort 等高阶函数。

六、operator 模块:函数化的运算符

operator 模块把运算符变成函数,可以传参:

1
2
3
4
5
6
7
8
from operator import add, mul, gt, itemgetter, methodcaller

reduce(add, [1, 2, 3, 4]) # 10
reduce(mul, [1, 2, 3, 4]) # 24
list(filter(partial(gt, 5), [1, 3, 5, 7, 9])) # 小于 5 的(因为 gt(5, x))

# 调用方法
sorted(words, key=methodcaller("lower"))

七、函数作为返回值:函数工厂

1
2
3
4
5
6
7
8
9
def make_greeter(greeting):
def greet(name):
return f"{greeting}, {name}"
return greet

hello = make_greeter("Hello")
hi = make_greeter("Hi")
print(hello("咖飞")) # Hello, 咖飞
print(hi("小王")) # Hi, 小王

这本质就是闭包(作用域与闭包 讲过)。

八、装饰器预告

高阶函数最重要的应用就是装饰器——一个”接受函数、返回新函数”的函数:

1
2
3
4
5
6
7
8
9
10
def uppercase(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper

@uppercase
def greet(name):
return f"hello {name}"

print(greet("咖飞")) # HELLO 咖飞

我们在 装饰器入门 会详细讲。

九、函数式编程的边界

Python 支持函数式编程,但它不是纯函数式语言。跟 Haskell 或 Lisp 相比:

  • Python 更强调命令式和面向对象;
  • 没有尾调用优化,不适合大量递归;
  • lambda 有限制,不能像 Haskell 那样自由;
  • 推导式在很多场景比 map/filter 更 Pythonic。

建议:把函数式当”工具”,不当”信仰”。合适的地方用,别为了”函数式”而扭曲代码。

十、一个综合例子:数据管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# pipeline.py
"""对一批数据做多步转换,展示 lambda / map / filter / sorted / reduce 的组合。"""

from functools import reduce
from operator import itemgetter, mul

records = [
{"name": "咖飞", "score": 88, "gender": "M", "class": "A"},
{"name": "小王", "score": 75, "gender": "M", "class": "B"},
{"name": "小李", "score": 92, "gender": "F", "class": "A"},
{"name": "小张", "score": 60, "gender": "M", "class": "B"},
{"name": "小陈", "score": 95, "gender": "F", "class": "A"},
]

# 1. 挑 A 班
class_a = filter(lambda r: r["class"] == "A", records)

# 2. 只留 name 和 score
projected = map(lambda r: (r["name"], r["score"]), class_a)

# 3. 按分数降序
ranked = sorted(projected, key=itemgetter(1), reverse=True)

print("A 班排行榜:", ranked)

# 4. 所有女生分数乘积(示意 reduce)
females = filter(lambda r: r["gender"] == "F", records)
prod = reduce(mul, map(itemgetter("score"), females))
print("女生分数积:", prod)

# 5. 及格用户的名字(推导式版)
passed = [r["name"] for r in records if r["score"] >= 60]
print("及格:", passed)

# 6. 平均分
avg = sum(map(itemgetter("score"), records)) / len(records)
print(f"平均分:{avg:.1f}")

十一、常见陷阱

  1. lambda 里想写多行/if-return:不行,改回 def。
  2. 给 lambda 命名:PEP 8 反对。
  3. 循环里创建 lambda 的晚绑定:见 作用域与闭包
  4. map/filter 结果是迭代器filter(...) 只能遍历一次,多次遍历要 list()
  5. reduce 空序列reduce(add, []) 抛错,除非给 initial。
  6. partial 位置参数只能从左到右固定:想固定中间的参数用关键字:partial(f, y=10)
  7. operator.itemgetter 传多个索引:返回元组:itemgetter(0, 2)("abc") == ('a', 'c')

十二、小结与延伸阅读

  • lambda 只写一个表达式,一般用来传给别的函数;
  • 高阶函数:map / filter / reduce / sorted / min / max / any / all;
  • 现代 Python 更爱推导式,除非用现成函数;
  • operator.itemgetter / attrgetter 是 sort key 的最佳选择;
  • functools.partial 用来”预配置”函数;
  • 函数是一等公民 → 可以进容器、传参、返回;
  • 别过度函数式,能推导式就推导式。

延伸阅读:

下一篇 装饰器入门 我们把”高阶函数 + 闭包”合体,写出真正的 Pythonic 装饰器。