字数 1149,阅读大约需 6 分钟
Python functools 全解析

写Python代码久了,你肯定会遇到这种场景:一个函数计算量很大,每次调用都像在等外卖;某个函数参数太多,调用时总得查文档;写个装饰器,结果原函数的 __name__ 和 __doc__ 全丢了。
这些问题,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:固定参数,减少调用成本
有些函数参数太多,每次调用都要传一串相同的值。比如你要用 int 函数把一堆字符串转成十六进制整数:
data = ['1a', 'ff', 'dead', 'beef']
result = [int(x, 16) for x in data] # 每次都要写 16
用 partial 把 base=16 固定住:
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 创建的是一个 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__、__annotations__ 等属性复制到 wrapper 函数上。这样,被装饰的函数就「看起来」还是原来的函数。
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 可以把比较函数转成 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:缓存函数结果,性能提升利器
下次写代码遇到「性能差、参数多、装饰器丢信息」这些问题时,记得先想想 functools 有没有现成的解决方案。标准库里藏着不少宝藏,就看你有没有耐心去挖了。