functools 是 Python 标准库中的一个模块,它提供了用于高阶函数(即操作其他函数的函数)的工具,这个模块里的函数可以帮助你更灵活、更强大地使用函数。

functools 的核心思想是函数式编程,它鼓励将函数视为一等公民(可以作为参数传递、作为返回值、存储在变量中)。
下面,我将分点介绍 functools 中最常用和最重要的几个函数。
functools.partial - 偏函数
partial 的作用是“冻结”一个函数的部分参数,从而创建一个新的、参数更少的函数,这在需要固定某些参数的场景下非常有用。
想象一个场景: 你有一个函数,它需要多个参数,但你经常在调用它时只改变其中一个参数,而其他参数保持不变。

语法:
functools.partial(func, *args, **keywords)
func: 要绑定的函数。*args: 要绑定的位置参数(从左到右绑定)。**keywords: 要绑定的关键字参数。
示例 1:固定一个参数
import functools
# 原始函数,需要两个参数
def power(base, exponent):
return base ** exponent
# 创建一个新的函数,固定 exponent 为 2
square = functools.partial(power, exponent=2)
# 现在调用 square 只需要一个参数
print(square(4)) # 输出: 16
print(square(5)) # 输出: 25
# 也可以固定位置参数
cube = functools.partial(power, 3) # 固定 base 为 3
print(cube(4)) # 输出: 81 (3**4)
print(cube(2)) # 输出: 9 (3**2)
示例 2:在回调函数中常用
假设你有一个 GUI 库,按钮点击事件会调用一个函数,并自动传入一个事件对象作为参数,但你自己的处理函数不需要这个事件对象。
import functools
# 模拟一个按钮点击事件处理器
def button_click_handler(command, event):
print(f"执行命令: {command}")
# event 对象在这里被忽略了
# 创建一个处理 "save" 命令的回调函数
# 当按钮被点击时,它会自动调用 save_click_handler(event)
# 我们用 partial 预先填充了 command 参数
save_click_handler = functools.partial(button_click_handler, command="save")
# 模拟按钮被点击
# event = some_event_object
save_click_handler(event=None) # 输出: 执行命令: save
partial 可以让你的代码更简洁,避免重复传递相同的参数,特别是在处理回调函数时非常有用。
functools.wraps - 装饰器辅助函数
当你自己写一个装饰器时,原始函数的元信息(如函数名 __name__、文档字符串 __doc__ 等)会丢失。wraps 可以帮助你将这些元信息“复制”到装饰器内部的函数上。
问题示例:
def my_decorator(func):
def wrapper(*args, **kwargs):
"""Wrapper function's docstring."""
print("函数执行前...")
result = func(*args, **kwargs)
print("函数执行后...")
return result
return wrapper
@my_decorator
def example_function():
"""This is the docstring for example_function."""
print("我是 example_function")
print(example_function.__name__) # 输出: wrapper
print(example_function.__doc__) # 输出: Wrapper function's docstring.
可以看到,example_function 的元信息被 wrapper 函数覆盖了,这对于调试和代码文档化非常不利。
使用 wraps 解决问题:
from functools import wraps
def my_decorator(func):
@wraps(func) # 使用 wraps 装饰器
def wrapper(*args, **kwargs):
"""Wrapper function's docstring."""
print("函数执行前...")
result = func(*args, **kwargs)
print("函数执行后...")
return result
return wrapper
@my_decorator
def example_function():
"""This is the docstring for example_function."""
print("我是 example_function")
print(example_function.__name__) # 输出: example_function
print(example_function.__doc__) # 输出: This is the docstring for example_function.
在编写任何装饰器时,都应该在内部函数上使用 @wraps(func),这是一个好习惯,能让你的代码更易于维护和调试。
functools.lru_cache - 缓存装饰器
lru_cache 是一个非常实用的装饰器,它可以为函数提供缓存(Memoization)功能,它会记住最近调用过的参数和对应的返回结果,当再次使用相同的参数调用该函数时,它会直接从缓存中返回结果,而无需重新计算。
LRU 的意思是 Least Recently Used (最近最少使用),当缓存满时,它会淘汰最久未被使用的数据。
语法:
@functools.lru_cache(maxsize=None, typed=False)
maxsize: 缓存的大小,如果设置为None,则缓存大小无限制,如果设置为 0,则禁用缓存,推荐设置为 2 的幂次方(如 128, 256)。typed: 如果设置为True,会将不同类型的参数区分开(1和0会被视为不同参数)。
示例:计算斐波那契数列
这是一个经典的例子,因为它包含大量的重复计算。
from functools import lru_cache
import time
# 不使用缓存
def fibonacci_no_cache(n):
if n < 2:
return n
return fibonacci_no_cache(n-1) + fibonacci_no_cache(n-2)
# 使用缓存
@lru_cache(maxsize=128)
def fibonacci_with_cache(n):
if n < 2:
return n
return fibonacci_with_cache(n-1) + fibonacci_with_cache(n-2)
# 性能对比
start_time = time.time()
fibonacci_no_cache(35)
end_time = time.time()
print(f"不使用缓存耗时: {end_time - start_time:.4f} 秒")
start_time = time.time()
fibonacci_with_cache(35)
end_time = time.time()
print(f"使用缓存耗时: {end_time - start_time:.4f} 秒")
# 查看缓存信息
print(fibonacci_with_cache.cache_info())
# 输出: CacheInfo(hits=34, misses=36, maxsize=128, currsize=36)
# hits: 缓存命中次数
# misses: 未命中次数(即实际计算的次数)
从结果可以看出,使用 lru_cache 后,性能提升是巨大的。
lru_cache 是优化纯函数(相同输入总是产生相同输出)性能的利器,特别适用于计算密集型且存在重复调用的函数。
functools.reduce - 归约函数
reduce 函数用于对一个序列(如列表、元组)中的所有元素进行累积操作,最终将整个序列“归约”为一个单一的值。
语法:
functools.reduce(function, iterable[, initializer])
function: 一个二元函数,接受两个参数,并返回一个结果。iterable: 一个可迭代对象。initializer(可选): 初始值,如果提供,计算会从初始值开始;否则,序列的第一个元素会被用作初始值。
示例 1:计算列表所有元素的和
from functools import reduce numbers = [1, 2, 3, 4, 5] # reduce 会这样执行: # (((1 + 2) + 3) + 4) + 5 total = reduce(lambda x, y: x + y, numbers) print(total) # 输出: 15 # 使用初始值 10 total_with_init = reduce(lambda x, y: x + y, numbers, 10) print(total_with_init) # 输出: 25 (10 + 1 + 2 + 3 + 4 + 5)
示例 2:查找列表中的最大值
from functools import reduce numbers = [10, 50, 2, 100, 35] # reduce 会这样执行: # max(max(max(max(10, 50), 2), 100), 35) max_value = reduce(lambda x, y: x if x > y else y, numbers) print(max_value) # 输出: 100
与 sum()、max() 的对比:
对于像求和、求最大值、求最小值这类常见操作,Python 内置的 sum()、max()、min() 函数通常更直观、更高效。reduce 更通用,可以处理任何累积逻辑。
reduce 是一个强大的工具,用于将一个序列“折叠”成一个值,虽然很多场景下有更简单的替代方案,但当你需要一个通用的累积逻辑时,reduce 是不二之选。
functools.singledispatch - 泛函数
Python 3.4+ 引入了 singledispatch,它允许你根据函数第一个参数的类型来分发到不同的实现上,这被称为单分派。
这在不修改原有函数的情况下,为不同的数据类型实现不同的逻辑非常有用。
语法:
@functools.singledispatch
def func(arg):
# 默认实现
pass
@func.register
def _(arg: int):
# 针对 int 类型的实现
pass
@func.register
def _(arg: list):
# 针对 list 类型的实现
pass
注意: 注册函数的函数名不重要,通常使用 _。
示例:为不同类型的数据实现“漂亮打印”
from functools import singledispatch
@singledispatch
def pretty_print(obj):
"""默认的打印方式"""
print(f"默认打印: {repr(obj)}")
@pretty_print.register
def _(obj: int):
"""为整数类型定制的打印方式"""
print(f"这是一个整数: {obj}")
@pretty_print.register
def _(obj: list):
"""为列表类型定制的打印方式"""
print(f"这是一个列表,包含 {len(obj)} 个元素: {obj}")
@pretty_print.register
def _(obj: str):
"""为字符串类型定制的打印方式"""
print(f"这是一个字符串,长度为 {len(obj)}: '{obj}'")
# 调用
pretty_print(123) # 输出: 这是一个整数: 123
pretty_print([1, 2, 3]) # 输出: 这是一个列表,包含 3 个元素: [1, 2, 3]
pretty_print("hello") # 输出: 这是一个字符串,长度为 5: 'hello'
pretty_print({"a": 1}) # 输出: 默认打印: {'a': 1}
singledispatch 是实现多态的一种优雅方式,特别适合在库或框架中为不同数据类型扩展功能,而无需创建复杂的继承体系。
其他重要成员
functools.total_ordering: 这是一个类装饰器,如果你在一个类中定义了__eq__和__lt__(或其他比较方法中的一个),@total_ordering会自动为你填充剩下的所有比较方法 (__le__,__gt__,__ge__),让你免于编写重复的代码。
| 函数/类 | 用途 | 典型场景 |
|---|---|---|
partial |
固定函数的部分参数,创建新函数。 | 简化回调函数、创建特定功能的函数。 |
wraps |
保留原始函数的元信息 (__name__, __doc__ 等)。 |
编写任何装饰器时的必备工具。 |
lru_cache |
缓存函数结果,避免重复计算。 | 优化纯函数性能,特别是递归或计算密集型函数。 |
reduce |
将序列归约为单一值。 | 自定义累积逻辑(虽然常见操作有内置函数替代)。 |
singledispatch |
根据第一个参数的类型分发到不同函数。 | 为不同数据类型扩展函数功能,实现多态。 |
functools 模块是 Python 高级编程的瑞士军刀,熟练掌握它能让你的代码更加优雅、高效和富有表现力。
