-
-
Save Morreski/c1d08a3afa4040815eafd3891e16b945 to your computer and use it in GitHub Desktop.
from datetime import datetime, timedelta | |
import functools | |
def timed_cache(**timedelta_kwargs): | |
def _wrapper(f): | |
update_delta = timedelta(**timedelta_kwargs) | |
next_update = datetime.utcnow() + update_delta | |
# Apply @lru_cache to f with no cache size limit | |
f = functools.lru_cache(None)(f) | |
@functools.wraps(f) | |
def _wrapped(*args, **kwargs): | |
nonlocal next_update | |
now = datetime.utcnow() | |
if now >= next_update: | |
f.cache_clear() | |
next_update = now + update_delta | |
return f(*args, **kwargs) | |
return _wrapped | |
return _wrapper |
The implementation has a big problem: if you have a function that you can call with different values and you obviously want the result cached with TTL for each calling value, then when the TTL is reached for one calling value, the cache is cleared of ALL CACHED RESULTS, that is FOR ALL CALLING VALUES.
Sample code:
import time
import random
@timed_cache(seconds=10)
def expensive_operation(a: int):
return random.randint(1, 1 + a)
def ex_op_wrapper(a: int):
return f'{time.time()}: {expensive_operation(a)}'
Calling in reply with a 6 secs pause between the first and second call:
ex_op_wrapper(1000)
'1657014039.3334417: 762'
ex_op_wrapper(100)
'1657014045.5532942: 4'
ex_op_wrapper(1000)
'1657014047.3158472: 762'
ex_op_wrapper(100)
'1657014048.6246898: 4'
ex_op_wrapper(1000)
'1657014049.7079725: 847'
ex_op_wrapper(100)
'1657014050.7649162: 70'
You can see that the first cached result for calling with 100 was 4 at '1657014045.5532942', then that was changed at '1657014050.7649162' to 70, so only 5 secs after the first caching of 4, instead of 10.
The problem in the above code is that f.cache_clear()
clears the cache for all calling values, not just for the expired one.
Thanks guys ! Btw it can leads to a TypeError: unhashable type: 'list'
if you have list args.
A fix could be to cast those args to tuple (more info : https://stackoverflow.com/a/49210802)
This piece of code will fix this :
for arg, value in kwargs.items():
kwargs[arg] = tuple(value) if type(value) == list else value
Thanks guys ! Btw it can leads to a
TypeError: unhashable type: 'list'
if you have list args. A fix could be to cast those args to tuple (more info : https://stackoverflow.com/a/49210802) This piece of code will fix this :for arg, value in kwargs.items(): kwargs[arg] = tuple(value) if type(value) == list else value
The behavior remains the same but I would suggest to use isinstance()
instead of type()
for arg, value in kwargs.items():
kwargs[arg] = tuple(value) if isinstance(value, list) else value
Thanks for the implementations! Really helpful!
Something I noticed is that neither of these implementations work with pytest-antilru. This is likely due to the lru_cache
which is monkeypatched is not patched early enough: ipwnponies/pytest-antilru#28.
args = [tuple(v) if isinstance(v, list) else v for v in args]
for args too
Thanks guys ! Btw it can leads to a
TypeError: unhashable type: 'list'
if you have list args. A fix could be to cast those args to tuple (more info : https://stackoverflow.com/a/49210802) This piece of code will fix this :for arg, value in kwargs.items(): kwargs[arg] = tuple(value) if type(value) == list else valueThe behavior remains the same but I would suggest to use
isinstance()
instead oftype()
for arg, value in kwargs.items(): kwargs[arg] = tuple(value) if isinstance(value, list) else value
@jianshen92 👌💪