1. 传入逻辑
普通函数通常接收数据,然后按照函数内部写好的步骤处理数据:
1def double(number):2return number * 234print(double(3)) # 6
如果希望同一个函数可以处理不同规则,就可以把「逻辑片段」也作为参数传进去。下面的 apply_twice() 不关心具体要做什么,只负责把传入的动作执行两次:
1def add_exclamation(text):2return text + "!"34def apply_twice(value, action):5return action(action(value))67print(apply_twice("你好", add_exclamation)) # 你好!!
这里的 action 不是一个普通数据,而是一个可以被调用的函数。apply_twice() 把执行时机和执行次数负责好,把具体逻辑交给调用方,这就是「把行为传给函数」的基本思路。
2. 函数对象
在 Python 中,函数也是对象,可以像字符串、数字一样被赋值、放进列表、作为参数传入,或者作为返回值交给其他代码:
1def add(left, right):2return left + right34operation = add5operations = [add, lambda left, right: left - right]67print(operation(2, 3))8print(operations[0](5, 2)) # 79print(operations[1](5, 2)) # 3
注意,add 和 add() 含义不同:
add表示函数对象本身,可以传给另一个函数;add()表示立即调用函数,括号中的值是传入的实参。
当一个函数接收另一个函数作为参数,或者返回一个函数时,就可以称为高阶函数。高阶函数并不神秘,它只是利用了 Python「函数也是值」这一特性。
函数返回函数时,可以把一部分配置提前保存下来。这个模式也叫函数工厂:
01def make_multiplier(factor):02def multiply(number):03return number * factor0405return multiply0607double = make_multiplier(2)08triple = make_multiplier(3)0910print(double(4)) # 811print(triple(4)) # 12
make_multiplier() 返回了一个函数,同时这个函数记住了 factor。这正是上一节「闭包」中介绍的关系:高阶函数描述的是函数的传入或返回,闭包强调返回的函数还保存了外层变量。
3. 映射
map(function, iterable) 会把函数应用到可遍历对象的每个元素,并返回一个迭代器。需要查看全部结果时,通常使用 list() 转换:
1def to_upper(text):2return text.upper()34topics = ["变量", "函数", "类"]5result = map(to_upper, topics)67print(list(result)) # ['变量', '函数', '类']
map() 不会直接修改原列表,也不会立刻把所有结果保存成新列表。迭代器被消费后就不能再次从头读取:
1numbers = [1, 2, 3]2result = map(lambda number: number * 2, numbers)34print(list(result)) # [2, 4, 6]5print(list(result)) # []
如果转换逻辑很简单,列表推导式通常更直观:
1numbers = [1, 2, 3]2result = [number * 2 for number in numbers]34print(result) # [2, 4, 6]
map() 适合把一个已经命名的处理函数应用到整组数据;列表推导式适合在一行中表达简单转换。两者的重点都是「每个元素都得到一个对应结果」。
4. 筛选与汇总
filter(function, iterable) 会保留函数返回真值的元素。传入的函数通常叫谓词(predicate),它只负责回答「这个元素是否符合条件」:
1def is_adult(age):2return age >= 1834ages = [12, 18, 20, 15, 30]5adults = filter(is_adult, ages)67print(list(adults)) # [18, 20, 30]
和 map() 一样,filter() 返回的是惰性迭代器。条件简单时,也可以使用列表推导式:
1ages = [12, 18, 20, 15, 30]2adults = [age for age in ages if age >= 18]34print(adults) # [18, 20, 30]
reduce() 用来把一组元素逐步合并成一个结果。它位于 functools 模块中,传入的函数每次接收「当前累计值」和「下一个元素」:
1from functools import reduce23def add(total, number):4return total + number56numbers = [1, 2, 3, 4]7total = reduce(add, numbers, 0)89print(total) # 10
上面的计算过程可以理解为 (((0 + 1) + 2) + 3) + 4。第三个参数 0 是初始累计值,它还能让空列表有明确的结果:
1from functools import reduce23numbers = [2, 3, 4]4product = reduce(lambda total, number: total * number, numbers, 1)56print(product) # 24
reduce() 很灵活,但不一定是最易读的选择。求和优先使用 sum(),求最大值或最小值优先使用 max()、min();只有在确实需要按顺序累积自定义结果时,才使用 reduce()。
5. 排序
sorted(iterable, key=None, reverse=False) 会返回一个新的列表,不会修改原来的可遍历对象。key 接收一个函数,用来告诉 Python 比较每个元素的哪个值:
1scores = [80, 95, 68, 90]23print(sorted(scores)) # [68, 80, 90, 95]4print(sorted(scores, reverse=True)) # [95, 90, 80, 68]
如果元素是字典或对象,可以使用 key 提取排序依据:
1students = [2{"name": "小明", "score": 88},3{"name": "小红", "score": 95},4{"name": "小刚", "score": 82},5]67ordered = sorted(students, key=lambda student: student["score"], reverse=True)8print(ordered)
sorted() 和列表的 sort() 不同:sorted() 返回新列表,可以处理任何可遍历对象;sort() 是列表的方法,会直接修改原列表,并返回 None:
1numbers = [3, 1, 2]2result = numbers.sort()34print(numbers) # [1, 2, 3]5print(result) # None
key 函数只负责为每个元素提供比较值,不需要自己实现排序算法。Python 的排序是稳定的:当两个元素的 key 相同时,它们会保留原来的相对顺序。
6. 组合
map()、filter() 和 sorted() 可以组合起来,把数据处理过程写成清晰的步骤。下面先筛选成年人,再按年龄从大到小排序:
01users = [02{"name": "小明", "age": 17},03{"name": "小红", "age": 23},04{"name": "小刚", "age": 20},05]0607adults = filter(lambda user: user["age"] >= 18, users)08ordered = sorted(adults, key=lambda user: user["age"], reverse=True)0910print(list(ordered))
当步骤较多时,可以先把每一步命名为普通函数,再传给高阶函数。命名函数更容易调试和复用:
01users = [02{"name": "小明", "age": 17},03{"name": "小红", "age": 23},04{"name": "小刚", "age": 20},05]0607def is_adult(user):08return user["age"] >= 180910def age_key(user):11return user["age"]1213adults = filter(is_adult, users)14ordered = sorted(adults, key=age_key, reverse=True)
lambda 适合很短的单表达式逻辑,例如 lambda user: user["age"]。如果逻辑需要分支、异常处理或多行说明,就应该写成普通函数。高阶函数的目标是把「处理流程」和「具体规则」分开,而不是为了少写几行代码而强行嵌套表达式。
在 FastAPI 和 LangChain 项目中,这种思想会出现在请求校验、消息转换、工具回调和链式处理里:框架负责调用流程,开发者把某个步骤的具体行为以函数形式传进去。