Implementing TTL Cache in Python (1)
Overview
In this article, we’ll discuss how to implement a caching mechanism in Python that has a Time-To-Live (TTL).
This cache can be useful in scenarios like:
- Functions that connect to the internet to fetch data, but the data remains constant over a period.
(e.g., weather forecast data) - Functions that are executed frequently but don’t require real-time data.
Adding Caching to a Function
First, let’s see how to add caching to a Python function.
This can be achieved using the lru_cache
decorator from functools
(official documentation).
cache
decorator behaves the same as lru_cache(maxsize=None)
.-
A function to find Fibonacci numbers can be implemented as below, but without caching, it’s slow:
def fibonacci(n): """Returns the nth Fibonacci number.""" if n == 0: return 0 elif n == 1: return 1 else: return fibonacci(n - 2) + fibonacci(n - 1)
-
Example:
%time fibonacci(40) # CPU times: user 22.1 s, sys: 0 ns, total: 22.1 s # Wall time: 22.1 s # 102334155
-
-
By using the
lru_cache
decorator to cache the function’s output,
the computation becomes much faster.
(This process is often referred to as “memoization”.)from functools import lru_cache @lru_cache(maxsize=None) def fibonacci(n): """Returns the nth Fibonacci number.""" if n == 0: return 0 elif n == 1: return 1 else: return fibonacci(n - 2) + fibonacci(n - 1)
-
Example:
%time fibonacci(40) # CPU times: user 52 µs, sys: 1 µs, total: 53 µs # Wall time: 58.9 µs # 102334155
-
Implementing a Cache with TTL
So, how can we implement caching with TTL?
We can archive this by combining:
- A function that changes its return value at specified intervals.
- A dummy argument.
We’ll explain with the example below.
(This idea is based on an answer from StackOverflow).
-
Consider a function that fetches content from a specified URL:
@lru_cache(maxsize=None) def get_content(url): """Fetch content from a URL.""" ...
-
Add a new dummy argument
dummy
to this function:@lru_cache(maxsize=None) def get_content(url, dummy): """Fetch content from a URL.""" ...
-
Feed the return value of a function
get_ttl_hash
that changes its return value at specified intervals, to this dummy parameter:hash = get_ttl_hash() get_content(url, dummy=hash)
-
With this approach, the function arguments remain the same for the duration of the TTL, utilizing the cache. Once the TTL expires, the argument changes, and the cache is bypassed, forcing a fresh computation.
A function that changes its return value at specified intervals can be implemented as follows:
import datetime
def get_ttl_hash(seconds=3600):
"""Calculate hash value for TTL caching.
Args:
seconds (int, optional): Expiration time in seconds. Defaults to 3600.
Returns:
int: Hash value.
"""
utime = datetime.datetime.now().timestamp()
return round(utime / (seconds + 1))
Conclusion
Now we have a function with TTL caching capabilities.
However, continually setting up a dummy argument and assigning get_ttl_hash
to it can be tedious. So, in the next part, we’ll implement a decorator that does this automatically for us.