functools 是 Python 标准库中处理函数和可调用对象的高阶工具集合。原文列出的核心导入包括 reduce、partial、lru_cache、wraps、total_ordering、singledispatch、cmp_to_key、cached_property。它常用于缓存优化、偏函数、装饰器元信息保留、函数重载和比较方法补全。
一、partial:预填充参数生成新函数
partial(func, *args, **kwargs) 可以固定部分参数,创建一个新的可调用对象,相当于 Python 中函数柯里化的一种实现。基础示例如下:
- from functools import partial
- def power(base, exponent):
- return base ** exponent
- square = partial(power, exponent=2)
- cube = partial(power, exponent=3)
- print(square(10)) # 100
- print(cube(10)) # 1000
复制代码
在实际回调场景中,可以用 partial 固定 logger 名称,再继续固定 event_type 和时间戳,减少重复传参:
- from functools import partial
- import time
- def log_event(logger, event_type, message, timestamp):
- print(f'[{timestamp}] {event_type}: {message} (logger={logger})')
- app_log = partial(log_event, 'app')
- app_log_error = partial(app_log, 'ERROR', timestamp=time.time())
- app_log_error('数据库连接失败')
- # [1718000000.0] ERROR: 数据库连接失败 (logger=app)
复制代码
数据处理管道也适合用 partial 生成专用转换函数:
- from functools import partial
- def transform(data, multiplier, offset, round_digits=2):
- return round(data * multiplier + offset, round_digits)
- c_to_f = partial(transform, multiplier=9/5, offset=32, round_digits=1)
- f_to_c = partial(transform, multiplier=5/9, offset=-32*5/9, round_digits=1)
- print(c_to_f(0)) # 32.0
- print(c_to_f(100)) # 212.0
- print(f_to_c(32)) # 0.0
复制代码
二、lru_cache 与 cache:缓存函数结果
lru_cache 用于最近最少使用缓存;cache 是 Python 3.9+ 提供的无限制缓存,等价于 lru_cache(maxsize=None)。用 lru_cache 改写递归函数,可以避免重复子问题计算:
- from functools import lru_cache
- import time
- @lru_cache(maxsize=128)
- def fibonacci(n):
- if n < 2:
- return n
- return fibonacci(n - 1) + fibonacci(n - 2)
- start = time.perf_counter()
- print(fibonacci(35))
- print(f'首次: {time.perf_counter() - start:.4f}秒')
- start = time.perf_counter()
- print(fibonacci(35))
- print(f'缓存: {time.perf_counter() - start:.6f}秒')
- print(fibonacci.cache_info())
- # CacheInfo(hits=34, misses=36, maxsize=128, currsize=36)
- fibonacci.cache_clear()
复制代码
使用 lru_cache 时要注意:被缓存函数的参数必须可哈希;lru_cache 装饰器不能直接用于方法,否则可能导致内存泄漏。数据库查询缓存是常见场景:
- from functools import lru_cache
- @lru_cache(maxsize=256)
- def get_user_by_id(user_id):
- print(f'查询数据库: user_id={user_id}')
- return {'id': user_id, 'name': f'用户{user_id}'}
- print(get_user_by_id(1)) # 查询数据库
- print(get_user_by_id(1)) # 缓存命中,不查数据库
复制代码
三、wraps:保留被装饰函数的元信息
写装饰器时,如果不使用 wraps,原函数的 __name__、__doc__ 等元信息会被 wrapper 覆盖:
- from functools import wraps
- def bad_decorator(func):
- def wrapper(*args, **kwargs):
- '''这是wrapper的文档'''
- return func(*args, **kwargs)
- return wrapper
- @bad_decorator
- def greet(name):
- '''向用户打招呼'''
- return f'Hello, {name}!'
- print(greet.__name__) # wrapper
- print(greet.__doc__) # 这是wrapper的文档
复制代码
加上 @wraps(func) 后,元信息能够正确保留:
- from functools import wraps
- def good_decorator(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- '''这是wrapper的文档'''
- return func(*args, **kwargs)
- return wrapper
- @good_decorator
- def greet_v2(name):
- '''向用户打招呼'''
- return f'Hello, {name}!'
- print(greet_v2.__name__) # greet_v2
- print(greet_v2.__doc__) # 向用户打招呼
复制代码
因此,@wraps 基本是编写装饰器时的标配,不加会增加调试和文档阅读成本。
四、singledispatch:按类型分派的函数重载
singledispatch 可以根据第一个参数类型选择不同实现,类似单分派函数重载:
- from functools import singledispatch
- @singledispatch
- def format_output(data):
- return str(data)
- @format_output.register(list)
- def _(data):
- return '[' + ', '.join(format_output(item) for item in data) + ']'
- @format_output.register(dict)
- def _(data):
- items = [f'{k}: {format_output(v)}' for k, v in data.items()]
- return '{' + ', '.join(items) + '}'
- @format_output.register(int)
- def _(data):
- return f'{data:,}'
- print(format_output(1234567)) # 1,234,567
- print(format_output([1, 'hello', [2, 3]])) # [1, hello, [2, 3]]
- print(format_output({'name': '张三', 'age': 25})) # {name: 张三, age: 25}
复制代码
五、cached_property:只计算一次的惰性属性
cached_property 把耗时计算变成实例属性风格访问,第一次访问时计算并缓存,后续访问直接取缓存:
- from functools import cached_property
- import time
- class DataAnalyzer:
- def __init__(self, data):
- self.data = data
- @cached_property
- def statistics(self):
- print('正在计算统计数据...')
- time.sleep(0.5)
- return {
- 'sum': sum(self.data),
- 'avg': sum(self.data) / len(self.data),
- 'min': min(self.data),
- 'max': max(self.data),
- }
- analyzer = DataAnalyzer([1, 2, 3, 4, 5])
- print(analyzer.statistics) # 计算,并打印“正在计算...”
- print(analyzer.statistics) # 缓存命中,不再打印
复制代码
它与 property 的区别在于:cached_property 像实例属性一样直接访问,同时只计算一次。
六、total_ordering:自动补全比较方法
total_ordering 适合实现版本号等可比较对象。只要定义 __eq__ 和 __lt__,其余比较方法会自动生成:
- from functools import total_ordering
- @total_ordering
- class Version:
- def __init__(self, major, minor, patch):
- self.major = major
- self.minor = minor
- self.patch = patch
- def __eq__(self, other):
- return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)
- def __lt__(self, other):
- return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
- def __repr__(self):
- return f'v{self.major}.{self.minor}.{self.patch}'
- v1 = Version(1, 2, 3)
- v2 = Version(2, 0, 0)
- print(v1 < v2) # True
- print(v1 <= v2) # True,自动生成
- print(v1 >= v2) # False,自动生成
- print(v1 != v2) # True,自动生成
复制代码
七、总结:functools 工具选择建议
原文章给出的常用工具可以归纳为:partial 固定参数并创建新函数;lru_cache 自动缓存,适合加速递归和重复调用;wraps 保留装饰函数的元信息;cached_property 惰性计算且只算一次;singledispatch 根据类型选择实现;total_ordering 补全所有比较方法。实践时可以按场景选择:写装饰器用 wraps,慢递归用 lru_cache,重复参数用 partial。 |