@@ -1175,41 +1175,98 @@ variants of :func:`functools.lru_cache`:
1175
1175
1176
1176
.. testcode ::
1177
1177
1178
- class LRU:
1178
+ from time import time
1179
1179
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)}
1181
1185
self.func = func
1182
1186
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
1184
1220
1185
1221
def __call__(self, *args):
1186
1222
if args in self.cache:
1187
- value = self.cache[args]
1188
1223
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
1195
1237
1196
1238
.. doctest ::
1197
1239
:hide:
1198
1240
1199
1241
>>> def square (x ):
1200
- ... return x ** 2
1242
+ ... return x * x
1201
1243
...
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)
1206
1260
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)
1210
1268
True
1211
1269
1212
-
1213
1270
:class: `UserDict ` objects
1214
1271
-------------------------
1215
1272
0 commit comments