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[0]):
return decorator(args[0])
elif args:
raise ValueError("Must supply the decorator arguments as keywords.")
return decorator
Example usages:
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
Note that f
was still cached, while g
was not, because the TTL is shorter than the time between calls.
cache
andcache_time
dictionaries of the arguments. \$\endgroup\$