问题: 如何创建
iterator_cache
装饰器?
@iterator_cache
def cheap_eval(numbers: Tuple[int]) -> Tuple[int]:
print("cheap eval of {numbers}")
return expensive_eval(numbers)
def expensive_eval(numbers: Tuple[int]) -> Tuple[int]:
time.sleep(5 + len(numbers)) # Example of consuming time
print(f"expensive eval of {numbers}")
return tuple(numb**2 for numb in numbers)
描述:我有库中的
expensive_eval
函数,我需要多次调用它。
我想创建使用缓存的
cheap_eval
并提供与 expensive_eval
相同的结果以减少总时间。
我的第一个想法是使用辅助函数
eval_one
和 @lru_cache
,但是为每个值调用 expensive_eval
并不好,因为每次调用 expensive_eval
都有 5 秒的常数项。
如何创建装饰器,使其记住所有可迭代输入而不是整个元组?
具有输入和所需输出的代码是:
# Input
print(cheap_eval([1, 2]))
print(cheap_eval([1, 3]))
print(cheap_eval([1, 2, 3]))
# Output without iterator_cache
# total time = 22 seconds
cheap eval of [1, 2]
expensive eval of [1, 2]
[1, 4]
cheap eval of [1, 3]
expensive eval of [1, 3]
[1, 9]
cheap eval of [1, 2, 3]
expensive eval of [1, 2, 3]
[1, 4, 9]
# Output with iterator_cache
# total time = 13 seconds
cheap eval of [1, 2]
expensive eval of [1, 2]
[1, 4]
cheap eval of [1, 3]
expensive eval of [3]
[1, 9]
cheap eval of [1, 2, 3]
[1, 4, 9]
因此,只需分离出未缓存的输入并在未见过的输入上调用昂贵的函数即可。使用它来更新缓存。然后使用缓存来填充输出。
from collections.abc import Sequence
_cache = {}
def cheap_eval(numbers: Sequence[int]) -> list[int]:
new = []
for n in numbers:
if n not in _cache:
new.append(n)
new_results = expensive_eval(new)
for n, r in zip(new, new_results):
_cache[n] = r
return [_cache[n] for n in numbers]
如果需要,您可以将上述逻辑抽象为装饰器。但这是基本想法。