I recently played around with a script that got some data from the Google API. As I didn't want to spam requests at the service (and potentially get blocked), I made this decorator, which caches the result of a function for a specified amount of time. Any call after the time-to-live (TTL) will call the function again.
This is the first decorator I wrote that takes an optional argument (the time to keep the cache). That code was taken from this StackOverflow answer by @Eric. I also couldn't abstain from using the new walrus operator (Python 3.8+), since I'm always looking for opportunities to use it in order to get a better feel for it.
Any and all advice on how to make this better or more readable are welcome.
from datetime import datetime from functools import wraps DEBUG = True def temporary_cache(*args, ttl=60): """A decorator that ensures that the result of the function call is cached for `ttl` seconds (default: 60). Warning: The returned object is stored directly, mutating it also mutates the cached object. Make a copy if you want to avoid that. """ def decorator(func): func.cache = None func.cache_time = datetime.fromordinal(1) @wraps(func) def inner(*args, **kwargs): if ((now := datetime.now()) - func.cache_time).total_seconds() > ttl: func.cache = func(*args, **kwargs) func.cache_time = now elif DEBUG: # for debugging, disable in production print("Cached", func.__name__) return func.cache return inner if len(args) == 1 and callable(args): return decorator(args) elif args: raise ValueError("Must supply the decorator arguments as keywords.") return decorator
import time @temporary_cache def f(): return datetime.now() @temporary_cache(ttl=1) def g(): return datetime.now() if __name__ == "__main__": print(f()) # 2020-05-12 10:41:18.633386 time.sleep(2) print(f()) # Cached f # 2020-05-12 10:41:18.633386 print(g()) # 2020-05-12 10:41:20.635594 time.sleep(2) print(g()) # 2020-05-12 10:41:22.636782
f was still cached, while
g was not, because the TTL is shorter than the time between calls.