Skip to content

Commit c860d30

Browse files
authored
More useful OrderedDict LRU recipes (GH-28164)
1 parent 9e31b39 commit c860d30

File tree

1 file changed

+76
-19
lines changed

1 file changed

+76
-19
lines changed

Doc/library/collections.rst

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,41 +1175,98 @@ variants of :func:`functools.lru_cache`:
11751175

11761176
.. testcode::
11771177

1178-
class LRU:
1178+
from time import time
11791179

1180-
def __init__(self, func, maxsize=128):
1180+
class TimeBoundedLRU:
1181+
"LRU Cache that invalidates and refreshes old entries."
1182+
1183+
def __init__(self, func, maxsize=128, maxage=30):
1184+
self.cache = OrderedDict() # { args : (timestamp, result)}
11811185
self.func = func
11821186
self.maxsize = maxsize
1183-
self.cache = OrderedDict()
1187+
self.maxage = maxage
1188+
1189+
def __call__(self, *args):
1190+
if args in self.cache:
1191+
self.cache.move_to_end(args)
1192+
timestamp, result = self.cache[args]
1193+
if time() - timestamp <= self.maxage:
1194+
return result
1195+
result = self.func(*args)
1196+
self.cache[args] = time(), result
1197+
if len(self.cache) > self.maxsize:
1198+
self.cache.popitem(0)
1199+
return result
1200+
1201+
1202+
.. testcode::
1203+
1204+
class MultiHitLRUCache:
1205+
""" LRU cache that defers caching a result until
1206+
it has been requested multiple times.
1207+
1208+
To avoid flushing the LRU cache with one-time requests,
1209+
we don't cache until a request has been made more than once.
1210+
1211+
"""
1212+
1213+
def __init__(self, func, maxsize=128, maxrequests=4096, cache_after=1):
1214+
self.requests = OrderedDict() # { uncached_key : request_count }
1215+
self.cache = OrderedDict() # { cached_key : function_result }
1216+
self.func = func
1217+
self.maxrequests = maxrequests # max number of uncached requests
1218+
self.maxsize = maxsize # max number of stored return values
1219+
self.cache_after = cache_after
11841220

11851221
def __call__(self, *args):
11861222
if args in self.cache:
1187-
value = self.cache[args]
11881223
self.cache.move_to_end(args)
1189-
return value
1190-
value = self.func(*args)
1191-
if len(self.cache) >= self.maxsize:
1192-
self.cache.popitem(False)
1193-
self.cache[args] = value
1194-
return value
1224+
return self.cache[args]
1225+
result = self.func(*args)
1226+
self.requests[args] = self.requests.get(args, 0) + 1
1227+
if self.requests[args] <= self.cache_after:
1228+
self.requests.move_to_end(args)
1229+
if len(self.requests) > self.maxrequests:
1230+
self.requests.popitem(0)
1231+
else:
1232+
self.requests.pop(args, None)
1233+
self.cache[args] = result
1234+
if len(self.cache) > self.maxsize:
1235+
self.cache.popitem(0)
1236+
return result
11951237
11961238
.. doctest::
11971239
:hide:
11981240

11991241
>>> def square(x):
1200-
... return x ** 2
1242+
... return x * x
12011243
...
1202-
>>> s = LRU(square, maxsize=5)
1203-
>>> actual = [(s(x), s(x)) for x in range(20)]
1204-
>>> expected = [(x**2, x**2) for x in range(20)]
1205-
>>> actual == expected
1244+
>>> f = MultiHitLRUCache(square, maxsize=4, maxrequests=6)
1245+
>>> list(map(f, range(10))) # First requests, don't cache
1246+
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
1247+
>>> f(4) # Cache the second request
1248+
16
1249+
>>> f(6) # Cache the second request
1250+
36
1251+
>>> f(2) # The first request aged out, so don't cache
1252+
4
1253+
>>> f(6) # Cache hit
1254+
36
1255+
>>> f(4) # Cache hit and move to front
1256+
16
1257+
>>> list(f.cache.values())
1258+
[36, 16]
1259+
>>> set(f.requests).isdisjoint(f.cache)
12061260
True
1207-
>>> actual = list(s.cache.items())
1208-
>>> expected = [((x,), x**2) for x in range(15, 20)]
1209-
>>> actual == expected
1261+
>>> list(map(f, [9, 8, 7])) # Cache these second requests
1262+
[81, 64, 49]
1263+
>>> list(map(f, [7, 9])) # Cache hits
1264+
[49, 81]
1265+
>>> list(f.cache.values())
1266+
[16, 64, 49, 81]
1267+
>>> set(f.requests).isdisjoint(f.cache)
12101268
True
12111269

1212-
12131270
:class:`UserDict` objects
12141271
-------------------------
12151272

0 commit comments

Comments
 (0)