在Python标准库中,functools模块虽然常被开发人员忽略,但它犹如一把精密的“瑞士军刀”,内置了多个能显著提升代码质量与运行效率的工具。无论是应对性能瓶颈、简化复杂函数调用,还是妥善保留函数的元信息,它都能提供优雅且高效的解决方案。

lru_cache:为函数添加缓存加速
先看一个经典的性能案例:递归计算斐波那契数列。
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print(fib(35)) # 等吧,能等到你怀疑人生
为什么如此缓慢?因为计算fib(35)时会重复求解大量子问题,例如fib(33)和fib(34),时间复杂度呈指数级增长,达到O(2^n)。
解决方案往往只需一行装饰器:
from functools import lru_cache
@lru_cache(maxsize=128)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print(fib(100)) # 瞬间出结果
lru_cache的原理非常直观:它将函数的调用参数与返回值缓存起来。当使用相同参数再次调用时,直接返回缓存结果,避免了重复计算。LRU(最近最少使用)策略则会在缓存满时,自动淘汰那些最久未被访问的条目。
使用时可关注以下几个参数:
maxsize=None:设置无限缓存(需谨慎,可能消耗大量内存)。maxsize=128:默认值,适用于大多数场景。- 直接使用
@lru_cache:等价于maxsize=128。
需要注意的是,被装饰函数的参数必须是可哈希的(如整数、字符串、元组),列表、字典等可变对象则不行,否则会引发错误。
partial:固定参数,简化函数调用
你是否遇到过参数繁多的函数,每次调用都要重复填入相同的值?例如,需要将一组字符串按十六进制解析为整数:
data = ['1a', 'ff', 'dead', 'beef']
result = [int(x, 16) for x in data] # 每次都要写 16
使用partial可以“冻结”部分参数,创建一个新的可调用对象:
from functools import partial
hex_to_int = partial(int, base=16)
result = [hex_to_int(x) for x in data] # 干净多了
这个特性在处理回调函数或配置固定参数时尤为实用。假设有一个网络请求函数:
def fetch(url, timeout, retry):
# 网络请求逻辑
pass
在某个特定场景下,希望所有请求都使用相同的超时和重试策略:
quick_fetch = partial(fetch, timeout=5, retry=1)
quick_fetch('https://api.example.com/data')
生成的partial对象行为就像普通函数,你还可以通过其.func、.args和.keywords属性查看原始函数及被绑定的参数。
wraps:装饰器的得力助手
自己编写装饰器时,一个常见问题是原始函数的“身份”信息(如函数名、文档字符串)会丢失:
def my_decorator(func):
def wrapper(*args, **kwargs):
print('调用前')
result = func(*args, **kwargs)
print('调用后')
return result
return wrapper
@my_decorator
def greet(name):
"""打招呼函数"""
print(f'Hello, {name}!')
print(greet.__name__) # 输出 wrapper,不是 greet
print(greet.__doc__) # 输出 None,原函数的文档丢了
这会给调试、生成文档或使用依赖函数名的工具带来麻烦。wraps装饰器正是用来解决这个问题的:
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('调用前')
result = func(*args, **kwargs)
print('调用后')
return result
return wrapper
@my_decorator
def greet(name):
"""打招呼函数"""
print(f'Hello, {name}!')
print(greet.__name__) # 输出 greet
print(greet.__doc__) # 输出「打招呼函数」
wraps的作用是将原函数的__name__、__doc__、__module__等元属性复制到装饰器内部的包装函数上,从而让被装饰的函数在外界看起来“完好如初”。
reduce:老朋友的新归宿
对于从Python 2迁移过来的开发者,reduce是个熟悉的老朋友,它在Python 3中被移到了functools模块中。它的作用是对序列中的元素进行累积操作。
from functools import reduce
nums = [1, 2, 3, 4, 5]
total = reduce(lambda x, y: x + y, nums)
print(total) # 15
当然,求和用内置的sum()更简洁。但reduce的威力在于它能处理任何二元累积操作,通用性更强:
# 连乘
product = reduce(lambda x, y: x * y, nums)
# 找最大值
maximum = reduce(lambda x, y: x if x > y else y, nums)
cmp_to_key:自定义排序的适配器
Python的sorted函数通常使用key参数来定义排序规则。但有时你可能已经写好了一个传统的比较函数(返回-1, 0, 1),不想再重写为key函数的形式。
这时,cmp_to_key就能派上用场,它可以将一个比较函数转换为sorted所需的key函数:
from functools import cmp_to_key
def compare(a, b):
# 自定义比较逻辑:按字符串长度排序
if len(a) > len(b):
return 1
elif len(a) < len(b):
return -1
return 0
words = ['apple', 'pie', 'banana', 'a']
sorted_words = sorted(words, key=cmp_to_key(compare))
print(sorted_words) # ['a', 'pie', 'apple', 'banana']
总结
回过头来看,functools模块提供的工具虽不张扬,却个个切中开发中的痛点:
- lru_cache:通过缓存机制优化函数性能,是应对重复计算的利器。
- partial:通过固定参数生成新函数,极大简化了复杂函数的调用成本。
- wraps:在编写装饰器时,完美保留原函数的元信息,避免信息丢失。
- reduce:提供通用累积操作的能力,适用各种二元运算场景。
- cmp_to_key:充当传统比较函数与现代排序接口之间的桥梁,方便代码复用。
因此,下次你在编码中遇到性能不佳、参数冗长或装饰器导致信息丢失等问题时,不妨先想想functools这个工具箱。标准库里往往藏着许多这样的宝藏,静待有心人去发现和运用。
