使用functools.lru_cache时如何将不同传参模式视为同一次调用?
解决方案
最符合Python风格的实现是基于标准库inspect模块对函数参数做归一化处理,封装自定义的缓存装饰器,统一不同传参模式的缓存键。
核心思路
利用inspect.signature获取函数的参数签名,将任意传入的位置参数、关键字参数绑定到签名上,自动对齐参数顺序、合并关键字参数顺序,生成标准化的参数元组作为缓存键,完全规避传参方式差异带来的缓存失效问题。
实现代码
以下是兼容原生lru_cache所有能力的通用装饰器实现:
import functools import inspect def normalized_lru_cache(maxsize=128, typed=False): def decorator(func): # 提前获取目标函数的参数签名 sig = inspect.signature(func) # 用原生lru_cache包裹参数归一化后的内部调用 @functools.lru_cache(maxsize=maxsize, typed=typed) def cached_func(*bound_args): return func(*bound_args[:-len(sig.parameters)], **dict(bound_args[-1])) @functools.wraps(func) def wrapper(*args, **kwargs): # 将任意传参形式绑定到函数签名,对齐参数顺序、补全默认值 bound = sig.bind(*args, **kwargs) bound.apply_defaults() # 对关键字参数排序后生成统一的缓存键,消除关键字传参顺序的影响 normalized_args = bound.args + tuple(sorted(bound.kwargs.items())) return cached_func(*normalized_args) # 暴露原生lru_cache的常用方法,和原生使用体验完全一致 wrapper.cache_info = cached_func.cache_info wrapper.cache_clear = cached_func.cache_clear return wrapper return decorator
测试效果
替换原代码中的lru_cache即可直接生效:
@normalized_lru_cache(maxsize=128) def test(a, b, *, c, d): print(f'Hello from function: ({a}, {b}, {c}, {d})') test(1, 2, c=42, d='answer') test(1, 2, d='answer', c=42) test(b=2, a=1, c=42, d='answer')
运行后仅会输出1次执行结果,后两次调用都会命中缓存,符合预期。
方案优势
- 完全兼容原生
functools.lru_cache的所有参数和工具方法,替换成本为0 - 无需修改原函数业务逻辑,仅替换装饰器即可生效
- 完全基于Python标准库实现,无第三方依赖,符合Pythonic设计规范
内容的提问来源于stack exchange,提问作者user13375075
相关产品推荐
相关产品推荐

