Skip to content

Python 高阶函数:把函数当"积木"来搭建程序

引言:乐高积木的启示

玩乐高时,你不需要自己造每块积木——只需要把现有的积木块组合起来,就能搭出城堡、飞船、机器人。

Python 的高阶函数(Higher-order Function)就是这个思路:把函数本身当作积木块,传给另一个函数,或者从另一个函数中返回。这种"函数套函数"的玩法,让代码的抽象能力和复用性达到全新高度。

这篇我们系统讲解四大核心高阶函数:map()reduce()filter()sorted(),每个都从定义、语法、实战、性能、误区五个维度彻底吃透。


一、什么是高阶函数?

1.1 函数也是"值"

先看一个看似奇怪的现象:

python
>>> abs(-10)
10

>>> abs          # 不加括号,是什么?
<built-in function abs>

>>> f = abs      # 把函数赋值给变量
>>> f(-10)
10               # 变量 f 现在就是 abs 函数本身

核心结论:在 Python 中,函数名就是变量,它指向一个函数对象。函数可以像数字、字符串一样被赋值、被传递。

生活化理解:函数就像一把钥匙——你可以把钥匙挂在墙上(赋值给变量),也可以把钥匙交给别人(作为参数传递),别人拿着这把钥匙就能开门(调用函数)。

1.2 高阶函数的定义

一个函数如果可以接收另一个函数作为参数,或者返回一个函数,它就是高阶函数。

python
def add(x, y, f):
    return f(x) + f(y)

print(add(-5, 6, abs))   # f 接收 abs 函数,结果:abs(-5) + abs(6) = 11

生活化理解:就像你去餐厅,不是点"红烧肉",而是说"给我做这道菜,用这种做法"——"这道菜"是数据,"这种做法"是函数,餐厅就是高阶函数。


二、map():批量加工的流水线

2.1 官方定义与设计初衷

map() 接收一个函数一个可迭代对象,把函数依次作用到每个元素上,返回一个迭代器

设计初衷来自函数式编程的"映射"思想:把一种数据批量转换成另一种数据,而不改变原数据。

2.2 语法格式

python
map(function, iterable, ...)
参数说明
function要应用的函数,接收一个元素,返回一个结果
iterable可迭代对象(list、tuple、str 等)
...可传多个 iterable,function 需对应多个参数

返回值:Python 3 中返回迭代器(Iterator),惰性计算。

2.3 基础用法

python
# 场景 1:数字平方
def square(x):
    return x * x

result = map(square, [1, 2, 3, 4, 5])
print(list(result))   # [1, 4, 9, 16, 25]

# 场景 2:数字转字符串
result = map(str, [1, 2, 3, 4, 5])
print(list(result))   # ['1', '2', '3', '4', '5']

# 场景 3:多个序列同时处理(Python 3 特性)
result = map(lambda x, y: x + y, [1, 2, 3], [10, 20, 30])
print(list(result))   # [11, 22, 33]

生活化理解map() 就像工厂流水线——原料(数据)从一头进去,机器(函数)统一加工,成品从另一头出来。

2.4 进阶实战

案例:批量处理用户输入

python
# 用户输入的名字大小写混乱,统一规范为首字母大写
def normalize(name):
    return name.capitalize()   # 首字母大写,其余小写

L1 = ['adam', 'LISA', 'barT', 'ALICE']
L2 = list(map(normalize, L1))
print(L2)   # ['Adam', 'Lisa', 'Bart', 'Alice']

案例:批量提取文件扩展名

python
import os

files = ['a.txt', 'b.py', 'c.jpg', 'd.pdf']
exts = list(map(lambda f: os.path.splitext(f)[1], files))
print(exts)   # ['.txt', '.py', '.jpg', '.pdf']

2.5 与列表推导式的对比

python
# map 写法
list(map(lambda x: x * x, range(10)))

# 列表推导式写法
[x * x for x in range(10)]
对比维度map()列表推导式
可读性函数名清晰时更简洁逻辑复杂时更直观
性能略快(C 实现)略慢(Python 解释)
惰性返回迭代器,省内存返回列表,占内存
多序列原生支持 map(f, seq1, seq2)需用 zip 配合
适用场景简单转换、大数据流复杂逻辑、需要索引时

经验法则:简单转换用 map(),复杂逻辑用列表推导式;处理超大数据流时优先 map()(惰性)。

2.6 常见误区

误区 1:忘记 map 返回迭代器,直接当列表用

python
result = map(str, [1, 2, 3])
print(result)         # <map object at 0x...>,不是列表!
print(result[0])      # TypeError: 'map' object is not subscriptable

纠正:需要列表时显式转换 list(result),或直接用 for 循环迭代。

误区 2:在迭代中修改原列表

python
L = [1, 2, 3]
result = map(lambda x: x * 2, L)
L.append(4)            # 危险!map 是惰性的,还没开始算
print(list(result))    # [2, 4, 6, 8] —— 4 也被算进去了

纠正:map 创建后不要修改原序列,或先转成列表再修改。

误区 3:多序列时长度不一致

python
list(map(lambda x, y: x + y, [1, 2], [10, 20, 30]))   # [11, 22]
# 多余的 30 被静默丢弃,不会报错!

纠正:长度不一致时,map 会在最短序列处停止。如需严格匹配,先用 zip 或检查长度。


三、reduce():累积汇总的折叠器

3.1 官方定义与设计初衷

reduce() 把一个二元函数(接收两个参数)累积作用到序列上,把序列"折叠"成一个值。

设计初衷来自函数式编程的"折叠/累积"思想:把一串数据通过某种规则合并成一个结果

注意:Python 3 中 reduce() 被移到了 functools 模块,需要显式导入。

3.2 语法格式

python
from functools import reduce

reduce(function, iterable[, initial])
参数说明
function二元函数,接收两个参数,返回一个值
iterable可迭代对象
initial可选,初始值。提供后,先计算 function(initial, first_element)

返回值:累积后的最终结果(单个值)。

3.3 执行原理

python
reduce(f, [x1, x2, x3, x4])
# 等价于
f(f(f(x1, x2), x3), x4)

生活化理解:就像滚雪球——先捏一个小雪球(x1),然后每遇到新雪(x2, x3...)就裹上一层,雪球越滚越大。

3.4 基础用法

python
from functools import reduce

# 场景 1:求和
def add(x, y):
    return x + y

print(reduce(add, [1, 3, 5, 7, 9]))   # 25

# 场景 2:序列转整数
def fn(x, y):
    return x * 10 + y

print(reduce(fn, [1, 3, 5, 7, 9]))   # 13579

# 场景 3:带初始值
print(reduce(add, [1, 2, 3], 100))    # 106 = 100+1+2+3

3.5 进阶实战

案例:手写 str2int

python
from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
          '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def str2int(s):
    def char2num(ch):
        return DIGITS[ch]
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

print(str2int('13579'))   # 13579

案例:手写 str2float

python
from functools import reduce

def str2float(s):
    # 拆分整数部分和小数部分
    parts = s.split('.')
    int_part = reduce(lambda x, y: x * 10 + y, map(int, parts[0]))
    float_part = reduce(lambda x, y: x * 10 + y, map(int, parts[1]))
    return int_part + float_part / (10 ** len(parts[1]))

print(str2float('123.456'))   # 123.456

案例:求列表最大值的"reduce 版"

python
from functools import reduce

def my_max(L):
    return reduce(lambda a, b: a if a > b else b, L)

print(my_max([3, 1, 4, 1, 5, 9, 2, 6]))   # 9

3.6 与同类实现的对比

场景reduce 写法推荐替代方案原因
求和reduce(add, L)sum(L)内置函数更快更清晰
求积reduce(mul, L)math.prod(L)Python 3.8+ 内置
字符串拼接reduce(add, strs)''.join(strs)join 是 C 实现,极快
找最大值reduce(max2, L)max(L)内置函数
复杂累积reduce(f, L)视情况无内置时才用 reduce

性能真相reduce() 是 Python 解释器逐次调用函数,性能通常低于内置的 sum()max() 等 C 实现函数。它的价值在于表达力,而非性能。

3.7 常见误区

误区 1:空列表没有初始值

python
reduce(lambda x, y: x + y, [])   # TypeError: reduce() of empty iterable with no initial value

纠正:空序列必须提供 initial 参数,或先检查长度。

误区 2:忘记 reduce 需要导入

python
reduce(add, [1, 2, 3])   # NameError: name 'reduce' is not defined

纠正from functools import reduce

误区 3:滥用 reduce 导致可读性下降

python
# 可读性极差的写法
reduce(lambda a, b: a + [b] if b not in a else a, L, [])

纠正:复杂逻辑优先用普通循环。Python 之禅:Readability counts(可读性很重要)。


四、filter():条件筛选的过滤网

4.1 官方定义与设计初衷

filter() 接收一个判断函数一个可迭代对象,把判断函数依次作用于每个元素,返回值为 True 的保留,False 的丢弃,返回迭代器。

设计初衷:从数据中筛出符合条件的子集,是"过滤"思想的函数式表达。

4.2 语法格式

python
filter(function, iterable)
参数说明
function判断函数,返回 True/False(或能转为布尔值的对象)
iterable可迭代对象

返回值:迭代器(惰性)。

4.3 基础用法

python
# 场景 1:只保留奇数
def is_odd(n):
    return n % 2 == 1

print(list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])))   # [1, 5, 9, 15]

# 场景 2:删除空字符串和空白字符串
def not_empty(s):
    return s and s.strip()   # None 和 '' 返回 False,' ' 经 strip() 变 '' 也返回 False

print(list(filter(not_empty, ['A', '', 'B', None, 'C', ' '])))
# ['A', 'B', 'C']

# 场景 3:function 传 None,过滤掉所有"假值"
print(list(filter(None, [1, 0, '', 'a', [], [1], None])))
# [1, 'a', [1]]

生活化理解filter() 就像安检门——人(数据)挨个过,安检仪(函数)扫描,合格的放行,不合格的拦下。

4.4 进阶实战:埃氏筛法求素数

这是 filter() 最经典的应用——用无限生成器 + 惰性过滤表示"全体素数":

python
def _odd_iter():
    """生成从 3 开始的无限奇数序列"""
    n = 1
    while True:
        n = n + 2
        yield n

def _not_divisible(n):
    """返回一个判断函数:x 不能被 n 整除"""
    return lambda x: x % n > 0

def primes():
    """生成全体素数的无限序列"""
    yield 2
    it = _odd_iter()          # 初始序列:3, 5, 7, 9, 11, ...
    while True:
        n = next(it)          # 取第一个数,必是素数
        yield n
        it = filter(_not_divisible(n), it)   # 筛掉 n 的倍数,生成新序列

# 打印 100 以内的素数
for n in primes():
    if n < 100:
        print(n)
    else:
        break

精妙之处filter() 返回的是惰性迭代器,每次 next(it) 时才真正计算。primes() 可以无限生成素数,内存占用却始终是 O(1)。

4.5 与列表推导式的对比

python
# filter 写法
list(filter(lambda x: x % 2 == 0, range(10)))

# 列表推导式写法
[x for x in range(10) if x % 2 == 0]
对比维度filter()列表推导式
可读性条件函数有名字时更清晰条件简单时更直观
性能略快(C 实现)略慢
惰性返回迭代器返回列表
复杂条件函数可以复用条件内联,不易复用
适用场景大数据流、条件需复用小数据、简单条件

4.6 常见误区

误区 1:filter 后忘记转列表

python
result = filter(is_odd, [1, 2, 3])
print(result)   # <filter object at 0x...>
print(len(result))   # TypeError: object of type 'filter' has no len()

纠正:需要长度或索引时,先 list(result)

误区 2:在 filter 中修改原序列

python
L = [1, 2, 3, 4, 5]
f = filter(lambda x: x > 2, L)
L.remove(3)
print(list(f))   # [4, 5] —— 3 被删除了!

纠正:filter 是惰性的,创建后修改原序列会影响结果。

误区 3:判断函数有副作用

python
def bad_filter(x):
    print(f"checking {x}")   # 副作用!
    return x > 0

f = filter(bad_filter, [1, -2, 3])
# 此时什么都没打印,因为 filter 是惰性的

纠正:filter 的条件函数应该是纯函数(无副作用),副作用操作请在 for 循环或 map 中做。


五、sorted():自定义规则的排序大师

5.1 官方定义与设计初衷

sorted() 对可迭代对象排序,返回新的列表。通过 key 参数传入函数,可以自定义排序规则。

设计初衷:排序的核心是比较,但不同数据类型的比较规则千差万别。key 函数把"怎么比"抽象出来,让 sorted() 专注于"怎么排"。

5.2 语法格式

python
sorted(iterable, *, key=None, reverse=False)
参数说明
iterable可迭代对象
key函数,作用于每个元素,返回用于比较的"键"
reverseTrue 为降序,False 为升序(默认)

返回值:新的排序后列表(不修改原序列)。

5.3 基础用法

python
# 场景 1:数字排序
print(sorted([36, 5, -12, 9, -21]))           # [-21, -12, 5, 9, 36]

# 场景 2:按绝对值排序
print(sorted([36, 5, -12, 9, -21], key=abs))  # [5, 9, -12, -21, 36]

# 场景 3:字符串按长度排序
print(sorted(['apple', 'fig', 'banana'], key=len))   # ['fig', 'apple', 'banana']

# 场景 4:忽略大小写排序
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower))
# ['about', 'bob', 'Credit', 'Zoo']

# 场景 5:反向排序
print(sorted([1, 2, 3], reverse=True))        # [3, 2, 1]

key 函数的原理sorted() 先把 key 作用于每个元素,得到"键"列表,然后按键排序,最后按对应关系返回原元素。

python
原始: [36, 5, -12, 9, -21]
key=abs 后: [36, 5, 12, 9, 21]
按键排序: [5, 9, 12, 21, 36]
返回原元素: [5, 9, -12, -21, 36]

5.4 进阶实战

案例:学生成绩多维度排序

python
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]

# 按名字排序
print(sorted(L, key=lambda t: t[0]))
# [('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]

# 按成绩从高到低
print(sorted(L, key=lambda t: t[1], reverse=True))
# [('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]

# 先按成绩,成绩相同再按名字(多级排序)
L2 = [('Adam', 92), ('Lisa', 88), ('Bob', 88), ('Bart', 66)]
print(sorted(L2, key=lambda t: (-t[1], t[0])))
# [('Adam', 92), ('Bob', 88), ('Lisa', 88), ('Bart', 66)]
# 技巧:成绩取负实现降序,名字正序

案例:字典列表按字段排序

python
students = [
    {'name': 'Alice', 'score': 85, 'age': 20},
    {'name': 'Bob', 'score': 92, 'age': 19},
    {'name': 'Charlie', 'score': 78, 'age': 21},
]

# 按分数排序
print(sorted(students, key=lambda s: s['score'], reverse=True))

# 按年龄,再按分数
print(sorted(students, key=lambda s: (s['age'], -s['score'])))

案例:自定义类排序

python
class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def __repr__(self):
        return f'{self.name}: {self.score}'

students = [Student('Alice', 85), Student('Bob', 92), Student('Charlie', 78)]
print(sorted(students, key=lambda s: s.score, reverse=True))
# [Bob: 92, Alice: 85, Charlie: 78]

5.5 与同类实现的对比

场景sorted()list.sort()适用建议
原列表需保留返回新列表,原列表不变原地排序,原列表改变sorted()
只需排序结果返回新列表返回 Nonesorted()
对任意可迭代对象支持(如 dict、set、str)仅 listsorted()
内存敏感大列表创建新列表,占内存原地排序,省内存list.sort()

性能sorted()list.sort() 底层都是 Timsort(Python 内置排序算法),时间复杂度 O(n log n),性能差异主要在是否复制列表

5.6 常见误区

误区 1:以为 sorted() 会修改原列表

python
L = [3, 1, 2]
sorted(L)
print(L)   # [3, 1, 2] —— 原列表没变!

纠正sorted() 返回新列表。如需原地排序,用 L.sort()

误区 2:key 函数返回不可比较的类型

python
sorted([1, '2', 3], key=lambda x: x)   # TypeError: '<' not supported between 'str' and 'int'

纠正:key 函数必须返回可比较的同类型值。混合类型需先统一转换。

误区 3:多级排序时逻辑写反

python
# 错误:先按名字,再按成绩
sorted(L, key=lambda t: (t[0], t[1]))

# 正确:先按成绩(主要),再按名字(次要)
sorted(L, key=lambda t: (t[1], t[0]))

纠正:元组比较从左到右,左边是主要排序键,右边是次要

误区 4:对字典用 sorted() 时期望排序 value

python
d = {'a': 3, 'b': 1, 'c': 2}
print(sorted(d))   # ['a', 'b', 'c'] —— 默认按 key 排序!

# 按 value 排序
print(sorted(d, key=lambda k: d[k]))   # ['b', 'c', 'a']

纠正sorted(dict) 默认排序的是键(key),不是值。


六、四大函数速查表

函数作用语法返回值惰性典型场景
map()批量转换map(f, seq)迭代器统一加工数据
reduce()累积折叠reduce(f, seq, init)单个值合并、聚合
filter()条件筛选filter(f, seq)迭代器过滤数据
sorted()排序sorted(seq, key=f, reverse=)新列表自定义排序

组合使用示例:找出 1~100 中所有偶数的平方,按从大到小排序,取前 3 个

python
from functools import reduce

result = sorted(
    map(lambda x: x * x, filter(lambda x: x % 2 == 0, range(1, 101))),
    reverse=True
)[:3]
print(result)   # [10000, 9604, 9216]

七、动手练习

  1. 基础题:用 map()['hello', 'world'] 变成 ['HELLO', 'WORLD']
  2. 进阶题:用 reduce()[3, 5, 7, 9] 的乘积。
  3. 挑战题:用 filter() 找出 1~1000 中所有回文数(如 121, 1331)。
  4. 思考题map(f, filter(g, seq))filter(g, map(f, seq)) 有什么区别?什么场景下用哪种顺序?

小结

  • 高阶函数:能接收函数作为参数,或返回函数的函数。函数在 Python 中是一等公民。
  • map():批量转换,返回惰性迭代器。简单转换优先用它,复杂逻辑用列表推导式。
  • reduce():累积折叠,返回单个值。优先用 sum()max()join() 等内置函数,无内置时才用 reduce。
  • filter():条件筛选,返回惰性迭代器。条件函数应为纯函数,无副作用。
  • sorted():自定义排序,key 函数是灵魂。多级排序用元组,注意主次顺序。
  • 核心心法:高阶函数是"组合优于继承"的函数式体现——把简单的小函数组合成复杂功能,代码更模块化、更可复用、更易测试。