如何为同时包含位置参数与关键字参数的函数实现缓存?自定义缓存方案的可行性验证
你的思路有一定合理性,但存在几个关键缺陷,没法完全满足「准确缓存不同参数组合结果」的需求,咱们拆解来看:
现有方案的问题
只取关键字参数的值,丢失了参数名的关联
比如你调用foo(a=1, b=2)和foo(b=2, a=1),在Python3.6+里kwargs会保留传入顺序,此时kwargs.values()会分别返回(1,2)和(2,1),生成的缓存键会不同,但这两个调用本质上是同一个参数组合(对应函数形参a=1, b=2),会导致缓存重复存储,浪费空间且无法命中已有缓存。再比如混合传参的情况:
foo(1, 2)和foo(a=1, b=2),前者args是(1,2)、kwargs为空,后者args为空、kwargs值是(1,2),生成的缓存键也会不同,但它们的实际参数完全一致,同样会导致缓存失效。未处理默认参数
调用foo(1,2)和foo(1,2,c=False)时,函数实际使用的参数是完全相同的,但你的方案里前者kwargs为空,后者kwargs包含c=False,生成的缓存键会不同,无法复用已有缓存。潜在的参数哈希问题
虽然你提到了字典不能当键,但如果args或kwargs里包含列表、字典这类不可哈希的类型,就算转成元组也会报错——不过这是缓存的通用限制,需要确保你的函数参数都是可哈希类型。
更可靠的实现方案
正确的思路应该是:把所有传入的参数(包括默认值)规范化为「形参名+对应值」的有序结构,不管用户用位置参数、关键字参数还是混合方式传参,只要最终的参数值一致,就生成相同的缓存键。
可以借助inspect模块解析函数签名,自动处理参数绑定和默认值:
import inspect def foo(a, b, c=False, d=0): # 初始化函数级缓存(第一次调用时创建) if not hasattr(foo, "cache"): foo.cache = {} # 获取函数签名,用于解析传入的参数 sig = inspect.signature(foo) # 绑定传入的args和kwargs,匹配到对应的形参 bound_args = sig.bind(*args, **kwargs) # 为未传入的参数填充默认值 bound_args.apply_defaults() # 生成缓存键:将有序的形参-值对转为元组(可哈希) cache_key = tuple(bound_args.arguments.items()) if cache_key in foo.cache: return foo.cache[cache_key] # 这里是你的实际业务逻辑,示例用简单计算代替 result = a + b + int(c) + d foo.cache[cache_key] = result return result
这个方案的优势:
- 不管用户用什么方式传参,只要最终的参数值(包括默认值)一致,就会生成相同的缓存键,确保缓存命中准确。
- 自动处理默认参数,无需手动维护默认值的判断逻辑。
- 保留了形参名和值的关联,不会出现参数混淆的情况。
总结
你的原始方案只在严格按照形参顺序传关键字参数、且从不依赖默认参数的极端场景下可行,但对于大多数实际调用场景(混合传参、默认参数复用),会出现缓存失效或重复存储的问题。上面的改进方案更通用、更可靠,能完全满足你的需求。
内容的提问来源于stack exchange,提问作者SUBZERO

