Python & AI Tutorials Logo
Python 编程

23. 一等函数与函数式技巧

在前面的章节中,我们学习了如何定义和调用函数(function)、如何使用形参(parameters)与实参(arguments),以及理解变量作用域(variable scope)。现在我们将探索一个让 Python 与众不同的强大特性:函数是一等对象(first-class objects)。这意味着函数可以像其他任何值一样被对待——存储在变量中、作为参数传递给其他函数,并且从函数中作为返回值返回。

这种能力开启了优雅的编程技巧,让代码更灵活、更可复用、更具表达力。我们将通过实际示例来探索如何利用一等函数,理解闭包(closure)(会“记住”其环境的函数),使用 lambda 表达式(lambda expressions)来简洁地定义函数,并应用 map()filter()any()all() 等内置函数高效地处理集合。

23.1) 函数作为一等对象

23.1.1) “一等”是什么意思

在 Python 中,函数是一等对象(first-class objects),这意味着它们可以:

  • 被赋值给变量
  • 存储在数据结构中(列表(list)、字典(dictionary)等)
  • 作为参数传递给其他函数
  • 作为值从其他函数返回

这与某些编程语言不同,在那些语言中函数具有特殊地位,不能像普通值一样被操作。在 Python 中,函数只是另一种对象类型,类似于整数、字符串或列表。

让我们看看实际效果:

python
# 定义一个简单函数
def greet(name):
    return f"Hello, {name}!"
 
# 将函数赋值给一个变量
say_hello = greet
 
# 通过新变量调用该函数
message = say_hello("Alice")
print(message)  # Output: Hello, Alice!
 
# 检查两个名字是否引用同一个函数
print(greet)      # Output: <function greet at 0x...>
print(say_hello)  # Output: <function greet at 0x...>
print(greet is say_hello)  # Output: True

注意,当我们写 say_hello = greet 时,我们并没有调用函数(没有括号)。我们是在创建一个新名称,它引用同一个函数对象。greetsay_hello 现在都指向同一个函数,我们可以使用 is 运算符来验证这一点。

23.1.2) 在数据结构中存储函数

由于函数是对象,我们可以把它们存储在列表、字典或任何其他集合中:

python
# 将运算操作存储在字典中的计算器
def add(x, y):
    return x + y
 
def subtract(x, y):
    return x - y
 
def multiply(x, y):
    return x * y
 
def divide(x, y):
    return x / y
 
# 将函数存储在字典中
operations = {
    '+': add,
    '-': subtract,
    '*': multiply,
    '/': divide
}
 
# 使用字典执行计算
num1 = 10
num2 = 5
operator = '*'
 
result = operations[operator](num1, num2)
print(f"{num1} {operator} {num2} = {result}")  # Output: 10 * 5 = 50

这种模式对于构建灵活的系统非常有用。与其编写很长的 if-elif 语句链来选择要调用哪个函数,不如在字典中查找合适的函数并直接调用它。

23.2) 将函数作为参数传递

23.2.1) 基本概念

一等函数最强大的用途之一,就是把函数作为参数传递给其他函数。这让我们能够编写灵活、可复用的代码,使其可以适配不同的行为。

下面是一个简单示例:

python
# 将另一个函数应用到某个值上的函数
def apply_operation(value, operation):
    """将作为参数接收的 operation 函数应用到 value 上。"""
    return operation(value)
 
# 不同的操作
def double(x):
    return x * 2
 
def square(x):
    return x * x
 
def negate(x):
    return -x
 
# 使用同一个 apply_operation 函数搭配不同的操作
number = 5
print(apply_operation(number, double))   # Output: 10
print(apply_operation(number, square))   # Output: 25
print(apply_operation(number, negate))   # Output: -5

apply_operation 函数并不知道、也不在意它正在执行什么具体操作。它只是调用传给它的那个函数。这种关注点分离让代码更模块化,也更容易扩展。

23.2.2) 使用自定义函数处理集合

一种常见模式是:使用作为参数传入的函数来处理集合中的每个元素:

python
# 使用给定函数处理列表中的每个元素
def process_list(items, processor):
    """将 processor 函数应用到列表中的每个元素。"""
    results = []
    for item in items:
        results.append(processor(item))
    return results
 
# 不同的处理函数
def uppercase(text):
    return text.upper()
 
def add_exclamation(text):
    return text + "!"
 
def get_length(text):
    return len(text)
 
# 用不同方式处理同一个列表
words = ["hello", "world", "python"]
 
print(process_list(words, uppercase))        # Output: ['HELLO', 'WORLD', 'PYTHON']
print(process_list(words, add_exclamation))  # Output: ['hello!', 'world!', 'python!']
print(process_list(words, get_length))       # Output: [5, 5, 6]

这种模式非常有用,以至于 Python 提供了 map()filter() 等内置函数来完成这类工作(我们会在 23.6 节中探讨它们)。

23.2.3) 通过提供 key 函数进行排序(简要介绍)

Python 的 sorted() 函数接受一个 key 参数——一个用于决定如何比较元素的函数:

python
# 按不同条件对学生排序
students = [
    {"name": "Alice", "grade": 85, "age": 20},
    {"name": "Bob", "grade": 92, "age": 19},
    {"name": "Charlie", "grade": 78, "age": 21},
    {"name": "Diana", "grade": 95, "age": 20}
]
 
# 提取成绩的函数
def get_grade(student):
    return student["grade"]
 
# 提取姓名的函数
def get_name(student):
    return student["name"]
 
# 按成绩排序(升序)
by_grade = sorted(students, key=get_grade)
print("Sorted by grade:")
for student in by_grade:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Charlie: 78
#   Alice: 85
#   Bob: 92
#   Diana: 95
 
# 按姓名排序(按字母顺序)
by_name = sorted(students, key=get_name)
print("\nSorted by name:")
for student in by_name:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Alice: 85
#   Bob: 92
#   Charlie: 78
#   Diana: 95

key 函数会对每个元素调用一次,它的返回值会用于比较。这比必须编写自定义排序逻辑要灵活得多。

这种通过传入函数来自定义行为的模式在 Python 中极其常见。我们将在第 38 章探索更高级的排序技巧。

23.3) 从函数返回函数

23.3.1) 创建函数的函数

正如我们可以把函数作为参数传递一样,我们也可以从其他函数返回函数。这让我们能够动态创建专用函数:

python
# 创建并返回一个新函数的函数
def create_multiplier(factor):
    """创建一个将输入乘以给定因子的函数。"""
    def multiplier(x):
        return x * factor
    return multiplier
 
# 创建专用的乘法函数
double = create_multiplier(2)
triple = create_multiplier(3)
times_ten = create_multiplier(10)
 
# 使用创建出来的函数
print(double(5))      # Output: 10
print(triple(5))      # Output: 15
print(times_ten(5))   # Output: 50

这里发生了什么?create_multiplier 函数定义了一个名为 multiplier 的内部函数并返回它。每次我们用不同的因子调用 create_multiplier,都会得到一个新函数,它会“记住”那个特定因子。这是我们第一次直观地看到闭包(closures),我们会在下一节深入探讨。

23.3.2) 创建自定义验证器

返回函数对于创建自定义验证或处理函数尤其有用:

python
# 动态创建范围验证器
def create_range_validator(min_value, max_value):
    """创建一个函数,用于验证数字是否在指定范围内。"""
    def validator(number):
        return min_value <= number <= max_value
    return validator
 
# 创建特定验证器
is_valid_age = create_range_validator(0, 120)
is_valid_percentage = create_range_validator(0, 100)
is_room_temperature = create_range_validator(15, 30)
 
# 使用验证器
age = 25
print(f"Is {age} a valid age? {is_valid_age(age)}")  # Output: True
 
temp = 22
print(f"Is {temp}°C room temperature? {is_room_temperature(temp)}")  # Output: True
 
score = 150
print(f"Is {score} a valid percentage? {is_valid_percentage(score)}")  # Output: False

23.4) 理解闭包:会记住的函数

23.4.1) 什么是闭包?

闭包(closure) 是一种函数:它会“记住”创建它时所在作用域中的变量,即使那个作用域已经执行结束。在 23.3 节的示例中,我们其实已经在使用闭包,只是没有显式地称它们为闭包。

让我们看看闭包是如何工作的:

python
def create_counter(start=0):
    """创建一个能记住计数值的计数器函数。"""
    count = start  # 这个变量被闭包“捕获”
    
    def counter():
        nonlocal count  # 访问被捕获的变量
        count += 1
        return count
    
    return counter
 
# 创建两个互不影响的计数器
counter1 = create_counter(0)
counter2 = create_counter(100)
 
# 每个计数器维护自己的计数值
print(counter1())  # Output: 1
print(counter1())  # Output: 2
print(counter1())  # Output: 3
 
print(counter2())  # Output: 101
print(counter2())  # Output: 102
 
print(counter1())  # Output: 4 (counter1 is independent of counter2)

内部的 counter 函数对 count 变量形成了一个闭包。即使 create_counter 已经执行完毕,返回的 counter 函数仍然可以访问 count。每次调用 create_counter 都会创建一个新的、互相独立的闭包,并拥有各自的 count 变量。

23.4.2) 闭包如何捕获变量

当一个函数在另一个函数内部定义时,它可以访问外部函数作用域中的变量。这些变量会被“捕获”,即使外部函数返回后仍然可访问:

当 Python 创建内部函数时,它不仅会保存函数代码——还会保存内部函数所使用的外部函数变量的引用。这个过程称为“捕获(capturing)”变量。

python
def create_greeter(greeting):
    """创建一个使用自定义问候语的问候函数。"""
    def greet(name):
        return f"{greeting}, {name}!"
    return greet
 
# 创建不同的问候函数
say_hello = create_greeter("Hello")
say_hi = create_greeter("Hi")
say_bonjour = create_greeter("Bonjour")
 
# 每个问候函数都会记住自己特定的问候语
print(say_hello("Alice"))    # Output: Hello, Alice!
print(say_hi("Bob"))         # Output: Hi, Bob!
print(say_bonjour("Claire")) # Output: Bonjour, Claire!

greeting 参数会被闭包捕获。每个问候函数都有自己捕获的 greeting 值,并在每次调用时使用它。

23.4.3) 实用场景:配置型函数

闭包非常适合用来创建具有预配置行为的函数:

python
# 创建具有不同税率的价格计算器
def create_price_calculator(tax_rate):
    """创建一个应用特定税率的计算器。"""
    def calculate_total(price):
        tax = price * tax_rate
        return price + tax
    return calculate_total
 
# 为不同地区创建计算器
us_calculator = create_price_calculator(0.07)    # 7% tax
uk_calculator = create_price_calculator(0.20)    # 20% VAT
japan_calculator = create_price_calculator(0.10) # 10% consumption tax
 
# 计算不同地区的价格
item_price = 100
 
print(f"US total: ${us_calculator(item_price):.2f}")      # Output: US total: $107.00
print(f"UK total: £{uk_calculator(item_price):.2f}")      # Output: UK total: £120.00
print(f"Japan total: ¥{japan_calculator(item_price):.2f}") # Output: Japan total: ¥110.00

23.4.4) 何时使用闭包

当你需要以下能力时,闭包尤其有用:

  • 创建具有预配置行为的函数
  • 在不使用类的情况下,在函数调用之间保持状态
  • 实现需要记住上下文的回调函数(callback functions)
  • 创建生成专用函数的函数工厂(function factories)

23.5) 使用 lambda 编写简短的匿名函数

23.5.1) 什么是 Lambda 表达式?

lambda 表达式(lambda expression) 会创建一个小型的匿名函数——一个没有名字的函数。当你在短时间内需要一个简单函数,并且不想用 def 正式定义它时,lambda 表达式很有用。

语法是:

python
lambda parameters: expression

lambda 接受参数(像普通函数一样),并返回对表达式求值后的结果。下面是一个简单示例:

python
# 常规函数
def add(x, y):
    return x + y
 
# 等价的 lambda 表达式
add_lambda = lambda x, y: x + y
 
# 两者工作方式相同
print(add(3, 5))        # Output: 8
print(add_lambda(3, 5)) # Output: 8

lambda 表达式只能包含一个表达式——它们不能包含诸如 iffor 或多行代码这样的语句。这一限制让它们保持简单且聚焦。

23.5.2) 将 Lambda 表达式作为参数

当你需要把一个简单函数作为参数传递,又不想定义一个单独的具名函数时,lambda 表达式非常出彩:

python
# 使用 lambda 按成绩对学生排序
students = [
    {"name": "Alice", "grade": 85},
    {"name": "Bob", "grade": 92},
    {"name": "Charlie", "grade": 78},
    {"name": "Diana", "grade": 95}
]
 
# Instead of defining a separate function:
# def get_grade(student):
#     return student["grade"]
# sorted_students = sorted(students, key=get_grade)
 
# We can use a lambda directly:
sorted_students = sorted(students, key=lambda student: student["grade"])
 
print("Students sorted by grade:")
for student in sorted_students:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Charlie: 78
#   Alice: 85
#   Bob: 92
#   Diana: 95

当函数很简单且只使用一次时,这样会更简洁。lambda lambda student: student["grade"] 等价于一个接收学生并返回其成绩的函数。

23.5.3) 带多个参数的 Lambda

lambda 表达式可以像普通函数一样接受多个参数:

python
# 使用 lambda 实现计算器操作
operations = {
    'add': lambda x, y: x + y,
    'subtract': lambda x, y: x - y,
    'multiply': lambda x, y: x * y,
    'divide': lambda x, y: x / y if y != 0 else "Error"
}
 
# 使用这些 lambda 表达式
print(operations['add'](10, 5))       # Output: 15
print(operations['multiply'](10, 5))  # Output: 50
print(operations['divide'](10, 0))    # Output: Error

注意,我们可以在 lambda 中使用条件表达式(x / y if y != 0 else "Error"),但不能使用 if 语句(它需要多行代码)。

23.5.4) 何时使用 Lambda vs 具名函数

在以下情况下使用 lambda 表达式:

  • 函数非常简单(一个表达式)
  • 函数只使用一次,或只在非常局部的上下文中使用
  • 定义具名函数会带来不必要的冗长

在以下情况下使用具名函数:

  • 函数复杂或需要多个语句
  • 函数会在多个地方复用
  • 函数需要一个描述性名称来增强清晰度
  • 函数需要 docstring

23.5.5) Lambda 的限制与替代方案

lambda 表达式有一些重要限制:

python
# ❌ 这样不行 - lambda 不能包含语句
# bad_lambda = lambda x: 
#     if x > 0:
#         return x
#     else:
#         return -x
 
# ✅ 改用条件表达式
absolute_value = lambda x: x if x > 0 else -x
print(absolute_value(-5))  # Output: 5
print(absolute_value(3))   # Output: 3
 
# ✅ 对于多个操作,使用常规函数
def process_and_double(x):
    print(f"Processing: {x}")
    return x * 2
 
result = process_and_double(5)  # Output: Processing: 5
print(result)                    # Output: 10

lambda 表达式是针对特定场景的工具。当它们能让代码更清晰、更简洁时,就使用它们。当它们让代码更难理解时,就改用常规的具名函数。

23.6) 使用 map() 和 filter() 搭配简单函数

23.6.1) map() 函数

map() 函数会将给定的 function 应用到可迭代对象(iterable)(例如列表、元组或字符串)的每个元素上,并返回一个包含结果的迭代器(iterator)。它是一种在不编写显式循环的情况下,将集合中的每个元素进行转换的方法。

python
map(function, iterable, *iterables)

参数:

  • function(必需):一个接受一个或多个参数、对其进行处理并返回值的函数。该函数会对 iterable(s) 中的每个元素调用一次。
  • iterable(必需):一个序列(列表、元组、字符串等),其元素会被传入 function
  • *iterables(可选):用于多参数 function 的额外可迭代对象。

如果提供了多个 iterable,function 必须接受相同数量的参数
map() 会在最短的 iterable 被耗尽时停止

返回值:

一个 map 对象(迭代器),其中包含 function 对每个输入元素返回的结果。

重要map 对象是迭代器,而不是像 list 那样的序列。

python
# 将列表中的每个数字翻倍
numbers = [1, 2, 3, 4, 5]
 
def double(x):
    return x * 2
 
# 对每个数字应用 double
doubled = map(double, numbers)
result = list(doubled)  # 将 map 对象(迭代器)转换为列表
print(result)  # Output: [2, 4, 6, 8, 10]

23.6.2) 在 map() 中使用 Lambda

lambda 表达式与 map() 搭配非常合适,适用于简单的转换操作:

python
# 将温度从摄氏度转换为华氏度
celsius_temps = [0, 10, 20, 30, 40]
 
fahrenheit_temps = list(map(lambda c: (c * 9/5) + 32, celsius_temps))
print(fahrenheit_temps)  # Output: [32.0, 50.0, 68.0, 86.0, 104.0]

23.6.3) filter() 函数

filter() 函数会将给定的 function 应用到 iterable 的每个元素上,并返回一个只包含那些使函数返回 True 的元素的迭代器。它是一种在不编写显式循环的情况下,从集合中选择元素的方法。

python
filter(function, iterable)

参数:

  • function:一个接受一个参数、对其进行判断并返回 TrueFalse 的函数。该函数会对 iterable 中的每个元素调用一次。
  • iterable:一个序列(列表、元组、字符串等),其元素会被 function 测试。

返回值:

一个 filter 对象(迭代器),只包含那些 function 返回 True 的元素。

重要: filter 对象是迭代器,而不是像 list 那样的序列。

示例:

python
# 只保留偶数
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
def is_even(x):
    return x % 2 == 0
 
# 对每个数字应用 is_even,只保留返回 True 的数字
even_numbers = filter(is_even, numbers)
result = list(even_numbers)  # 将 filter 对象转换为列表
print(result)  # Output: [2, 4, 6, 8, 10]

23.6.4) 在 filter() 中使用 Lambda

lambda 表达式常与 filter() 一起使用,以实现更简洁的过滤:

python
# 筛选通过的学生(grade >= 60)
students = [
    {"name": "Alice", "grade": 85},
    {"name": "Bob", "grade": 55},
    {"name": "Charlie", "grade": 92},
    {"name": "Diana", "grade": 48},
    {"name": "Eve", "grade": 73}
]
 
passed = list(filter(lambda s: s["grade"] >= 60, students))
print("Students who passed:")
for student in passed:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Alice: 85
#   Charlie: 92
#   Eve: 73

23.6.5) 组合使用 map() 和 filter()

你可以把 map()filter() 串联起来,以完成更复杂的转换操作:

python
# 获取偶数的平方
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
# 先过滤出偶数,然后对其求平方
even_numbers = filter(lambda x: x % 2 == 0, numbers)
squared = map(lambda x: x ** 2, even_numbers)
result = list(squared)
print(result)  # Output: [4, 16, 36, 64, 100]

可视化对比:map() vs filter()

filter() - 保留部分元素

输入: [1, 2, 3, 4, 5]

测试: is_even(x)

输出: [2, 4](相等或更短)

map() - 转换所有元素

输入: [1, 2, 3, 4, 5]

应用: double(x) = x * 2

输出: [2, 4, 6, 8, 10](长度相同)

关键差异:

  • map():应用函数来转换每一个元素 → 输出长度相同
  • filter():测试每个元素,只保留通过测试的元素 → 输出长度相等或更短

在本章中,我们探索了 Python 强大的函数式编程(functional programming)特性。我们了解到函数是一等对象,可以像其他值一样被传递与操作,从而实现灵活且可复用的代码模式。我们发现函数可以返回其他函数,从而创建能记住其环境的闭包。我们探索了 lambda 表达式来简洁地定义函数,并使用 map()filter() 优雅地处理集合。

这些概念构成了高级 Python 编程技术的基础。在第 38 章中,我们将基于这些知识来掌握装饰器(decorators),这是 Python 最优雅的特性之一。

© 2025. Primesoft Co., Ltd.
support@primesoft.ai